为什么 Stream.allMatch()对于空流返回 true?

我的同事和我有一个 bug,这是由于我们假设调用 allMatch()的空流将返回 false

if (myItems.allMatch(i -> i.isValid()) {
//do something
}

当然,假设而不阅读文档是我们的错误。但是我不明白的是为什么空流的默认 allMatch()行为返回 true。这样做的理由是什么?与 anyMatch()(反过来返回 false)一样,这个操作是以一种命令式的方式使用的,与单子相分离,可能在 if语句中使用。考虑到这些事实,是否有任何理由为什么对于大多数使用而言,在空流上将 allMatch()默认设置为 true是可取的?

43724 次浏览

This is known as vacuous truth. All members of an empty collection satisfy your condition; after all, can you point to one that doesn't?

Similarly, anyMatch returns false, because you can't find an element of your collection that does match the condition. This is confusing to a lot of people, but it turns out to be the most useful and consistent way to define "any" and "all" for empty sets.

When I call list.allMatch (or its analogs in other languages), I want to detect if any items in list fail to match the predicate. If there are no items, none might fail to match. My following logic would pick items and expect them to have matched the predicate. For an empty list, I'll pick no items and the logic will still be sound.

What if allMatch returned false for an empty list?

My straightforward logic would fail:

 if (!myList.allMatch(predicate)) {
throw new InvalidDataException("Some of the items failed to match!");
}
for (Item item : myList) { ... }

I'll need to remember to replace the check with !myList.empty() && !myList.allMatch().

In short, allMatch returning true for an empty list is not only logically sound, it also lies on the happy path of execution, requiring fewer checks.

It looks like the base of it is mathematical induction. For computer science an application of this could be a base case of a recursive algorithm.

If the stream is empty, the quantification is said to be vacuously satisfied and is always true. Oracle Docs: Stream operations and pipelines

The key here is that it is "vacuously satisfied" which, by nature, is somewhat misleading. Wikipedia has a decent discussion about it.

In pure mathematics, vacuously true statements are not generally of interest by themselves, but they frequently arise as the base case of proofs by mathematical induction. Wikipedia: Vacuous Truth

Here's another way to think about this:

allMatch() is to && what sum() is to +

Consider the following logical statements:

IntStream.of(1, 2).sum() + 3 == IntStream.of(1, 2, 3).sum()
IntStream.of(1).sum() + 2 == IntStream.of(1, 2).sum()

This makes sense because sum() is just a generalization of +. However, what happens when you remove one more element?

IntStream.of().sum() + 1 == IntStream.of(1).sum()

We can see that it makes sense to define IntStream.of().sum(), or the sum of an empty sequence of numbers, in a particular way. This gives us the "identity element" of summation, or the value that, when added to something, has no effect (0).

We can apply the same logic to Boolean algebra.

Stream.of(true, true).allMatch(it -> it) == Stream.of(true).allMatch(it -> it) && true

More generically:

stream.concat(Stream.of(thing)).allMatch(it -> it) == stream.allMatch(it -> it) && thing

If stream = Stream.of() then this rule still needs to apply. We can use the "identity element" of && to solve this. true && thing == thing, so Stream.of().allMatch(it -> it) == true.

While this question has already been answered correctly multiple times, I want to bring in a more mathematical approach.

For that I want to consider the stream as a Set (in the mathematical sense). Then

emptyStream.allMatch(x-> p(x))

corresponds to enter image description here while

emtpyStream.anyMatch(x -> p(x))

corresponds to enter image description here.

That the second part is false is quite obvious since there are no elements in the empty set. The first one is a bit more tricky. You can either accept it to be true by definition or look into the other answers for some of the reasons why it should be that way.

An example to illustrate this difference are propositions like "All humans living on Mars have 3 legs" (true) and "There is a human living on Mars with 3 legs" (false)

Workaround not beautiful in some cases.

example:to verify if all requests are sent(but request list of some type can be empty)

public boolean isAllRequestsSent(String type){


//empty request List
var count = requestsList.stream().filter(Request::type).count();
        

if(count <= 0) {
return false;
}
    

return requestsList.stream().filter(Request::type).allMatch(RequestData::isSent);
}