Ogólnie predykat to wyrażenie, które określa, czy wartość może być prawdziwa lub fałszywa. W programowaniu predykaty to funkcje z jednym argumentem, które zwracają wartość boolean.
Interfejs funkcyjny predykat został wprowadzony w Javie 8. "Funkcyjny" oznacza, że zawiera tylko jedną abstrakcyjną metodę. Przyjmuje ona argument i zwraca boolean.
W Javie interfejsy funkcyjne są używane do obsługi wyrażeń Lambda oraz referencji do metod i konstruktorów. Zwykle predykat Java 8 służy do zastosowania filtru do kolekcji obiektów. Przyjrzyjmy się jego głównym zastosowaniom i szeroko stosowanym metodom oraz rozwiążmy niektóre problemy praktyczne.
Dlaczego deweloperzy używają predykatów
Zasadniczo programiści mogą używać predykatów do dowolnych zadań, które obejmują ocenę elementów w oparciu o wstępnie zdefiniowane kryteria i zwracaną wartość logiczną. Oto przykłady prostych zadań, które programiści wykonują za pomocą predykatów:- Filtrowanie zbioru liczb całkowitych.
- Sortowanie list poprzez zapewnienie zgodności danych z kilkoma predefiniowanymi warunkami (np. uporządkowanie grupy elementów przez ustalenie warunków cenowych i wagowych).
- Używanie pakietów narzędziowych w programowaniu współbieżnym.
Składnia predykatu w Javie
Interfejs java.util.function.Predicate został wprowadzony w Javie 8 jako alternatywny sposób na obsługę oceny wyświetleń w Lambdzie. Standardowy widok interfejsu to Predicate<T>, gdzie T jest pojedynczym argumentem zwracającym wartość logiczną. Predykat Java ma funkcyjną (abstrakcyjną) metodę test(Object), która ocenia predykat dla wskazanego obiektu.
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
Oto przykład prostego predykatu, który filtruje liczby całkowite na podstawie warunków "większe niż", "mniejsze niż".
// An example of a simple Java predicate
import java.util.function.Predicate;
public class PredicateExample {
public static void main(String[] args)
{
// Creating predicate
Predicate<Integer> lesserThan = i -> (i < 18);
// Calling Predicate method
System.out.println(lesserThan.test(10));
}
}
Wynikiem będzie true ponieważ 10 < 18.
Jeszcze jeden przykład z predykatem w filter(). Predykat pomaga odfiltrować wszystkich dorosłych z listy wiekowej.
import java.util.List;
import java.util.function.Predicate;
public class PredicateExample {
public static void main(String[] args) {
List<Integer> ages = List.of(17, 18, 19, 28, 18, 28, 46, 7, 8, 9, 21, 12);
NotLessThan18<Integer> isAdult = new NotLessThan18<>();
ages.stream().filter(isAdult).forEach(System.out::println);
}
}
class NotLessThan18<E> implements Predicate<Integer> {
@Override
public boolean test(Integer v) {
Integer ADULT = 18;
return v >= ADULT;
}
}
Wyświetlone zostanie:
18
19
28
18
28
46
21
Metody predykatów w Java 8
Interfejs Predykat udostępnia kilka metod.- boolean test(T t) ocenia predykat dla wskazanego argumentu.
- default Predicate<T> and(Predicate<? super T> other) zwraca predykat, który reprezentuje połączenie logiczne (AND) tego i innego predykatu.
- default Predicate<T> or zwraca predykat, który reprezentuje połączenie logiczne (OR) tego i innego predykatu.
- default Predicate<T> negate() zwraca predykat, który jest logicznym przeciwieństwem tego predykatu.
- default Predicate<T> isEqual(Object targetRef) zwraca wynik testu sprawdzającego, czy dwa argumenty są sobie równe zgodnie z Objects.equals(Object, Object).
boolean test(T t)
Jest to metoda funkcyjna dla predykatów Java, która ocenia, czy dany argument spełnia warunek predykatu. Przykład: tworzymy predykat adult dla każdego kto ma 18 lat lub więcej. Metoda test() pobiera wartość całkowitą i sprawdza ją.
import java.util.function.Predicate;
public class PredicateTestTest {
public static void main(String[] args) {
Predicate<Integer> adult = i -> i >= 18;
System.out.println(adult.test(12));
System.out.println(adult.test(19));
System.out.println(adult.test(21));
}
}
Jaki będzie wynik dla powyższego kodu? W pierwszym przypadku, ponieważ 12 jest mniejsze niż 18, będzie to false. W drugim i trzecim scenariuszu warunki są spełnione, zatem zwrócone zostanie true.
false
true
true
default Predicate.and()
Ta metoda reprezentuje operator "and". Dlatego jeśli jeden z wskazanych predykatów nie spełnia zadanego warunku, drugi nie będzie sprawdzany. Przykład: Stwórzmy za pomocą and() predykaty Java, które filtrują wszystkie dorosłe osoby, które jednocześnie mają mniej niż 65 lat. Użyjmy predicate.add () i napiszmy predykat Java z lambdą dla tych warunków: Jeżeli warunek jest spełniony, aplikacja zwróci następujące wyrażenie:
import java.util.function.Predicate;
public class PredicateDemo {
public static void main(String[] args) {
Predicate<Integer> adultYet = i -> i >= 18;
Predicate<Integer> adultStill = i -> i < 65;
System.out.println(adultYet.and(adultStill).test(5));
System.out.println(adultYet.and(adultStill).test(38));
System.out.println(adultYet.and(adultStill).test(90));
}
}
Wyświetlone zostanie:
false
true
false
default Predicate.or()
Metoda Predicate.or() reprezentuje operator "OR". Oznacza to, że warunek zostanie spełniony nawet jeśli tylko jeden z predykatów zwróci wartość true, a drugi false. Przykład: oceńmy ciągi znaków. Spróbuj użyć metody OR, aby wybrać wszystkie frazy zawierające podciąg "moja" lub "kredka".
import java.util.function.Predicate;
public class PredicateDemo2 {
public static void main(String[] args) {
Predicate<String> containsA = t -> t.contains("crayon");
Predicate<String> containsB = t -> t.contains("my");
System.out.println(containsA.or(containsB).test("here is my crayon"));
System.out.println(containsA.or(containsB).test("here is my pencil"));
System.out.println(containsA.or(containsB).test("here is John's crayon"));
System.out.println(containsA.or(containsB).test("here is John's pencil"));
}
}
Wyświetlone zostanie:
true
true
true
false
default Predicate negate()
Metoda negate() służy do zbierania wszystkich wartości, które nie spełniają wcześniej zdefiniowanych kryteriów. Przykład: jeśli chcesz posortować klasę "Tomatoes" i znaleźć wpisy, które nie są "Red", możesz napisać predykat i szybko przeskanować całą sekwencję. Spróbuj samodzielnie napisać kod tego predykatu i porównaj jego wynik z rozwiązaniem.
import java.util.function.Predicate;
public class PredicateDemo3 {
public static void main(String[] args) {
Predicate<Integer> adult = i -> i >= 18;
System.out.println(adult.negate().test(7)); System.out.println(adult.negate().test(19))
}
}
Wyświetlone zostanie:
true
false
static Predicate isEqual(Object targetRef)
Ta metoda jest bardzo przydatna, jeżeli chcesz sprawdzić, czy dwa obiekty są równe wartości zdefiniowanej jako parametr Objects.equals(). Ta metoda jest szczególnie przydatna, jeśli chcesz porównać podobne wyniki testów. Przykład: powiedzmy, że porównujesz dwa wózki z gruszkami i chcesz sprawdzić, czy oba zawierają owoce i standardowej wadze i kolorze. W tym przypadku static Predicate isEqual(Object targetRef) jest dokładnie tą metodą której potrzebujesz. Przyjrzyjmy się, jak isEqual sprawdza równość dwóch ciągów:
import java.util.function.Predicate;
public class PredicateDemo2 {
public static void main(String[] args) {
Predicate<String> i = Predicate.isEqual("here is my crayon");
System.out.println(i.test("here is my pencil"));
System.out.println(i.test("here is my crayon"));
}
}
Jeżeli analizowany obiekt spełnia standardowe warunki, funkcja zwróci true. Wyświetlone zostanie:
false
true
IntPredicate w Javie
IntPredicate to interfejs funkcyjny, zatem możesz użyć go jako cel przypisania w wyrażeniu lambda lub referencja do metody. IntPredicate operuje na liczbie całkowitej i zwraca wartość predykatu na podstawie warunku. Tak jak interfejs Predicate, IntPredicate również posiada metody test(), and(), negate(), or(). Oto przykład IntPredicate. Wybiera on z tablicy wszystkich dorosłych (18 lub więcej).
import java.util.Arrays;
import java.util.function.IntPredicate;
public class IntPredicateExample {
public static void main(String[] args) {
int[] ages = { 18, 28, 18, 46, 90, 45, 2, 3, 1, 5, 7, 21, 12 };
IntPredicate p = n -> n >= 18;
Arrays.stream(ages).filter(p).forEach(System.out::println);
}
}
Wyświetlone zostanie:
18
28
18
46
90
45
21
Pisanie czystych predykatów w Javie
Predykaty Java są bardzo funkcjonalne i przyjemnie się z nimi pracuje. Jednak jeśli nie masz świadomości, jak tworzone wyrażenia lambda wpływają na Twój kod, istnieje ryzyko, że kod stanie się trudny w utrzymaniu przez niechlujne predykaty. Oto kilka prostych praktyk, które sprawią, że Twoje interfejsy funkcyjne będą łatwe w zarządzaniu i czytaniu.- Nie powtarzaj się — nie twórz predykatów w Javie z powtarzającymi się wartościami, metodami i warunkami. W ten sposób marnujesz swój czas i sprawiasz, że kod jest zaśmiecony.
- Oddzielaj predykaty od kodu aplikacji, aby zapewnić testowalność. Poza tym utwórz harmonogram testów jednostkowych predykatów i trzymaj się go.
- Używaj importów i wzorców projektowych, aby mieć pewność, że Twoje klasy nie są rozdmuchane i będą łatwe w zarządzaniu.
- Rozważ przeniesienie predykatów Java do klas typu Helper — poprawisz przez to możliwość ponownego wykorzystania kodu i ułatwisz jego utrzymanie.
- Czytelność — gdy tylko jest to możliwe, preferuj instrukcje jednowierszowe zamiast złożonych predykatów. Może być kuszące, aby pochwalić się zrozumieniem złożonych interfejsów funkcyjnych. Jednak jeśli chodzi o utrzymywanie kodu, mniej znaczy więcej.
GO TO FULL VERSION