Why does Stream.allMatch() return true for an empty stream?

JavaLambdaJava 8Java Stream

Java Problem Overview


My colleague and I had a bug that was due to our assumption that an empty stream calling allMatch() would return false.

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

Of course, it is kind of our fault for assuming and not reading documentation. But what I don't understand is why the default allMatch() behavior for an empty stream returns true. What was the reasoning for this? Like the anyMatch() (which contrarily returns false), this operation is used in an imperative way that departs the monad and probably used in an if statement. Considering those facts, is there any reason why having allMatch() default to true on an empty stream be desirable for majority of uses?

Java Solutions


Solution 1 - Java

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 - Java

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 - Java

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 - Java

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

Solution 5 - Java

While this question has already been answered correctly multiply 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)

Solution 6 - Java

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);
		}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiontmnView Question on Stackoverflow
Solution 1 - Javauser2357112View Answer on Stackoverflow
Solution 2 - JavaHansView Answer on Stackoverflow
Solution 3 - Java9000View Answer on Stackoverflow
Solution 4 - JavaNathanView Answer on Stackoverflow
Solution 5 - JavaSchlaagiView Answer on Stackoverflow
Solution 6 - JavaBruno LeeView Answer on Stackoverflow