Dlaczego Stream.allMatch () zwraca wartość true dla pustego strumienia?


84

Mój kolega i ja mieliśmy błąd, który wynikał z naszego założenia, że ​​wywołanie pustego strumienia allMatch()powróci false.

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

Oczywiście to trochę nasza wina, że ​​przyjmujemy i nie czytamy dokumentacji. Ale nie rozumiem, dlaczego allMatch()powraca domyślne zachowanie pustego strumienia true. Jaki był tego powód? Podobnie jak anyMatch()(które odwrotnie zwraca false), operacja ta jest używana w sposób imperatywny, który odchodzi od monady i prawdopodobnie jest używany w ifinstrukcji. Biorąc pod uwagę te fakty, czy jest jakikolwiek powód, dla którego allMatch()domyślne ustawienie truepustego strumienia byłoby pożądane w większości zastosowań?


4
To trochę dziwne. Spodziewalibyśmy się, że jeśli allMatchzwraca prawdę, to powinno anyMatch. Dodatkowo dla pustej obudowy, allMatch(...) == noneMatch(...)co też jest dziwne.
Radiodef


Krótko o składni: zamiast pisać predykat jako i -> i.isValid(), możesz pisać Foo::isValid( Foooczywiście gdzie jest klasa, którą przesyłasz strumieniowo)
MattPutnam

2
„Ta operacja jest stosowana w sposób imperatywny, który odchodzi od monady” - wątpię w te czynniki przy jakichkolwiek decyzjach.
user253751

Odpowiedzi:


117

Jest to znane jako pusta prawda . Wszyscy członkowie pustej kolekcji spełniają twój warunek; w końcu czy możesz wskazać taki, który tego nie robi?

Podobnie anyMatchzwraca false, ponieważ nie możesz znaleźć elementu swojej kolekcji, który nie spełnia warunku. Dla wielu osób jest to mylące, ale okazuje się, że jest to najbardziej użyteczny i spójny sposób definiowania „dowolnych” i „wszystkich” dla pustych zestawów.


3
Rany, nienawidzę logiki boolowskiej. Myślę, że rozumiem, co mówisz. Brak negatywów jest pozytywny, ale brak pozytywów nie jest negatywny.
tmn

13
@ThomasN. W ten sam sposób wartość iloczynu pustego zbioru liczb jest, 1podczas gdy suma pustego zbioru liczb wynosi 0. Są neutralnymi elementami do mnożenia / dodawania. W przypadku booleanów masz to True and x = xi False or x = xdlatego jeśli uogólniasz andi orna sekwencje (to jest to alli anysą), kończysz z Truei Falsedla pustego przypadku, tj. Odpowiadają one elementom neutralnym.
Bakuriu

9
@ThomasN. anyMatchtesty na brak pozytywów, allMatchtesty na brak negatywów.
user253751

3
Zauważ, że w ten sposób możesz robić ładne rzeczy, takie jak prawo De Morgana: stream.allMatch (predykat) jest tym samym, co! Stream.anyMatch (predicate.negate ()). Podobnie! Stream.allMatch (predicate.negate ()) jest tym samym, co stream.anyMatch (predicate).
Hans

1
@PatrickBard: Możesz powiedzieć „czy możesz wskazać takiego, który to robi” i jest to całkowicie poprawne , ale oznacza to, że wszyscy członkowie kolekcji nie spełniają warunku. Wszyscy członkowie kolekcji spełniają warunek, a wszyscy członkowie kolekcji nie spełniają warunku. Są to inne stwierdzenia niż „nieprawdą jest, że wszyscy członkowie kolekcji spełniają warunek”. Jak powiedziałem, to zagmatwane.
user2357112 obsługuje Monikę z

10

Oto inny sposób myślenia o tym:

allMatch()jest do &&tego, co sum()jest+

Rozważ następujące logiczne stwierdzenia:

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

Ma to sens, ponieważ sum()jest to tylko uogólnienie +. Co się jednak stanie, gdy usuniesz jeszcze jeden element?

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

Widzimy, że sensowne jest zdefiniowanie IntStream.of().sum(), czyli suma pustego ciągu liczb, w określony sposób. Daje nam to „element tożsamości” sumowania, czyli wartość, która po dodaniu do czegoś nie ma żadnego efektu ( 0).

Możemy zastosować tę samą logikę do Booleanalgebry.

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

Bardziej ogólnie:

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

Jeśli stream = Stream.of()wtedy ta zasada nadal musi obowiązywać. Aby rozwiązać ten problem, możemy użyć „elementu tożsamości” funkcji &&. true && thing == thingwięc Stream.of().allMatch(it -> it) == true.


6

Kiedy dzwonię list.allMatch(lub jego odpowiedniki w innych językach), chcę wykryć, czy jakiekolwiek elementy listnie są zgodne z predykatem. Jeśli nie ma żadnych elementów, żaden może nie pasować. Moja następująca logika polegałaby na wybieraniu elementów i oczekiwaniu, że pasują do predykatu. W przypadku pustej listy nie wybiorę żadnych elementów, a logika będzie nadal działać.

A co, jeśli allMatchwrócisz falsez powodu pustej listy?

Moja prosta logika zawiodłaby:

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

Muszę pamiętać, żeby wymienić czek na !myList.empty() && !myList.allMatch().

Krótko mówiąc, allMatchpowrót truedo pustej listy jest nie tylko logicznie uzasadniony, ale leży również na szczęśliwej ścieżce wykonania, wymagającej mniejszej liczby kontroli.


prawdopodobnie miałeś na myśliif (!allMatch)
asylias

3

Wygląda na to, że podstawą tego jest indukcja matematyczna. W przypadku informatyki zastosowanie tego mogłoby być przypadkiem bazowym algorytmu rekurencyjnego.

Jeśli strumień jest pusty, mówi się, że kwantyfikacja jest pusto spełniona i zawsze jest prawdziwa. Oracle Docs: Streamuj operacje i potoki

Kluczowe jest tutaj to, że jest on „bezmyślnie zadowolony”, co z natury jest nieco mylące. Wikipedia ma o tym porządną dyskusję.

W czystej matematyce twierdzenia bezmyślnie prawdziwe nie są na ogół interesujące, ale często pojawiają się jako podstawa dowodu przez indukcję matematyczną. Wikipedia: Pusta prawda


1

Chociaż na to pytanie udzielono już poprawnej odpowiedzi, pomnożyć, chcę wprowadzić bardziej matematyczne podejście.

W tym celu chcę potraktować strumień jako zbiór (w sensie matematycznym). Następnie

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

odpowiada wprowadź opis obrazu tutajwhile

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

odpowiada wprowadź opis obrazu tutaj.

To, że druga część jest fałszywa, jest dość oczywiste, ponieważ w pustym zestawie nie ma elementów. Pierwsza jest nieco bardziej skomplikowana. Możesz zaakceptować to jako prawdę z definicji lub spojrzeć na inne odpowiedzi z kilku powodów, dla których tak powinno być.

Przykładem ilustrującym tę różnicę są twierdzenia typu „Wszyscy ludzie żyjący na Marsie mają 3 nogi” (prawda) i „Na Marsie żyje człowiek z 3 nogami” (fałsz)

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.