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

落花浮王杯 提交于 2019-12-17 07:26:38

问题


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?


回答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.




回答2:


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.




回答3:


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.




回答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



来源:https://stackoverflow.com/questions/30223079/why-does-stream-allmatch-return-true-for-an-empty-stream

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!