Why does Stream.allMatch() return true for an empty stream?
Solution 1:
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.
Solution 2:
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
.
Solution 3:
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.
Solution 4:
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