3 metody javy

Javy metody: anyMatch, noneMatch, allMatch

Chciałem podzielić się z Tobą krótką historią, jak trzy niepozorne metody mogą sprawić bardzo duże problemy. A chodzi o metody: anyMatch, noneMatch, allMatch, które pochodzą z klasy Stream. I nie chodzi o samo ich użycie, ale kontekst w jakim można je zastosować oraz to jakie konsekwencje będzie to miało dla Twojej aplikacji.

Metody te są do siebie podobne i sprawdzają, czy dane elementy strumienia pasują bądź nie do predykatu, który jest przekazywany jako parametr.

Stream.anyMatch

Metoda anyMatch(Predicate<? super T> predicate) służy do sprawdzenia, czy którykolwiek z elementów strumienia pasuje do predykatu. Jeśli tak, metoda zwraca true, jeśli nie to false.

IntStream numbers = IntStream.of(1, 2, 3, 4);
numbers.anyMatch(i -> i == 2); // true

 

Stream.noneMatch

Metoda noneMatch(Predicate<? super T> predicate) służy do sprawdzenia, czy żaden z elementów nie pasuje do predykatu. Jeśli żaden nie pasuje, metoda zwraca true, jeśli jakiś pasuje zwraca false.

IntStream numbers = IntStream.of(1, 2, 3, 4);
numbers.noneMatch(i -> i < 0); // true

 

Stream.allMatch

Metoda allMatch(Predicate<? super T> predicate) służy do sprawdzenie, czy wszystkie elementy strumienia pasują do predykatu. Jeśli pasują to metoda zwraca true, jeśli nie to false.

IntStream numbers = IntStream.of(1, 2, 3, 4);
numbers.allMatch(i -> i > 0); // true

 

Ale gdzie jest problem?

Na razie jest wszystko jasne, więc gdzie jest problem. Sprawa może się skomplikować w sytuacji, gdy wykorzystamy te metody nie do końca świadomie.

Wszystkie te metody zwracają boolean, więc można wykorzystać je w jakimś warunku lub w metodzie, która jest używana w jakimś warunku. Np.:

IntStream numbers = IntStream.of(1, 2, 3, 4);
if (numbers.allMatch(i -> i > 0)) {
  System.out.println("Wszystkie liczby są dodatnie");
}
// wydrukuje
Wszystkie liczby są dodatnie

 

A co w sytuacji, gdy strumień jest pusty?

IntStream numbers = IntStream.of();
if (numbers.allMatch(i -> i > 0)) {
  System.out.println("Wszystkie liczby są dodatnie");
}
// wydrukuje
Wszystkie liczby są dodatnie

Drukuje się to samo. Czy tego się spodziewałeś? Przecież nie ma żadnych liczb w strumieniu. Czy to jest poprawne zachowanie? Okazuje się, że tak.

Przyznam szczerze, że kiedyś błędnie założyłem, że w takiej sytuacji allMatch powinno zwrócić false. Na szczęście błąd nie miał poważnych konsekwencji, ale przecież mogło być inaczej…

 

Dlaczego powstał błąd?

Przeanalizujmy najpierw, jak zachowuje się metoda anyMatch w podobnej sytuacji.

Dla pustego strumienia anyMatch zwróci false. Czyli nie ma żadnego elementu, który by spełniał predykat. Jest to taka sama sytuacja, kiedy mamy w strumieniu elementy, z których żaden nie spełnia predykatu. I to mogła być jedna z przyczyn powstania tego błędu. Skoro anyMatch zwraca false, to można pomyśleć, że inne metody *Match będą zachowywały się podobnie.

Natomiast metoda noneMatch dla pustego strumienia zwróci już true. Żaden z elementów nie spełnia predykatu. Taką samą sytuację mamy, gdy w strumieniu mamy elementy, które nie spełniają predykatu.

Metoda allMatch, sprawdza czy wszystkie elementy strumienia pasują do predykatu. I w tym miejscu możesz pomyśleć, że skoro strumień jest pusty, to żaden element nie pasuje do predykatu, więc allMatch powinno zwracać false (i to mogło być kolejna przyczyna powstania tego błędu). Niestety allMatch zwraca true i jest to jak najbardziej prawidłowe zachowanie. Wynika to z faktu, że zastosowano tutaj Universal quantification. Gdzie przez konwencję, wszystkie elementy pustej kolekcji spełniają dany warunek i jest to znane jako Vacuous truth (zostało to opisane w JavaDoc dla tej metody).

Nie tylko ja miałem taki problem, na Stackoverflow jest wątek, w którym zostało to wyjaśnione.

 

Podsumowanie

Jak i czy można uniknąć takich błędów? Oczywiście, mógłbym napisać, że trzeba czytać dokumentację, JavaDoc itd., ale i taka wiedza może szybko ulecieć z naszej głowy.

Najlepsze rozwiązanie, które trwale może nas zabezpieczyć przed takimi błędami, to dobrze napisane testy jednostkowe. Dobre testy to takie, które testują wszystkie możliwe sytuacje. W tym przypadku mimo, że testy jednostkowe były, to zabrakło tego jednego, który by testował zachowanie metody w sytuacji gdy strumień jest pusty.

Jedną z ważniejszych kwestii w testach jednostkowych jest testowanie warunków brzegowych (pustych kolekcji, strumieni, pustych parametrów metod itd.) i warto mieć to zawsze na uwadze.

 

Żródła:

https://en.wikipedia.org/wiki/Universal_quantification

https://en.wikipedia.org/wiki/Vacuous_truth

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

https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html

 

Mateusz Dąbrowski

Cześć jestem Mateusz, zajmuję się programowaniem już ponad 12 lat z czego ponad 8 programuję w Javie. Zapraszam Cię do lektury mojego bloga. Możesz przeczytać więcej o mnie >>TUTAJ<<

One thought to “Javy metody: anyMatch, noneMatch, allMatch”

  1. Proponuję mnemoniki:
    allMatch zwraca true wtedy i tylko wtedy, gdy w strumieniu nie ma elementu, który by nie spełniał podanego warunku. Intuicyjny przykład:
    boolean isStreamVirusFree = stream.allMatch(T::isVirusFree);
    noneMatch zwraca true wtedy i tylko wtedy, gdy w strumieniu nie ma elementu, który by spełniał podany warunek. Intuicyjny przykład:
    boolean isStreamVirusFree = stream.noneMatch(T::isInfected);

Komentarze są zamknięte.