Spis treści:

Podstawy Pythona: Bezpłatny kurs dla wszystkich poziomów umiejętności Tworzenie portfolio: 4 angażujące projekty i interaktywna nauka Poznaj kurs: Czego możesz się spodziewać podczas kursu
Dowiedz się więcejUtwórzmy funkcje, które obliczą sumę i iloczyn dwóch liczb:
Teraz utwórzmy pojedynczą metodę i nazwijmy ją przetwórzDwieLiczby. Ta metoda przyjmuje dwa parametry numeryczne i kod do ich przetworzenia.
private int processTwoNumbers(int a, int b, [tutaj wpisz kod])
Podczas korzystania z metody sum, trzeci parametr przyjmuje operację a+b jako argument, podczas gdy w przypadku metody mult, ten parametr przyjmuje wartość a*b.
Warto zauważyć, że trzeci argument może być tylko kodem, który przyjmuje dwa parametry określonego typu (w naszym przypadku int) i generuje wynik pożądanego typu — int.
Powinniśmy w jakiś sposób powiadomić kompilator, że jest to konieczne, aby zapobiec przekazywaniu przez przyszłych programistów nieprawidłowego kodu (na przykład a+b+c).
W tym przypadku potrzebujemy sygnatury metody, która będzie działać jako trzeci parametr dla naszej metody processTwoNumbers.
private int computeSumOfTwoIntegers(int a, int b, [sygnatura metody])
Jak Czy możemy dodać trzeci parametr bez nadmiernego komplikowania i utrudniania odczytu sygnatury metody processTwoNumbers? Twórcy Javy znaleźli eleganckie rozwiązanie tego problemu. Opracowali koncepcję interfejsów funkcyjnych.
Definicja interfejsu funkcyjnego: kluczowe aspekty
Interfejs funkcyjny to interfejs zawierający tylko jedną metodę abstrakcyjną, która jest opisem metody bez jej implementacji. W tym przypadku metody statyczne i metody z domyślną implementacją nie są brane pod uwagę, a ich liczba w interfejsie funkcyjnym jest nieograniczona.
Jeśli metoda przyjmuje interfejs funkcyjny jako parametr, to podczas jej wywołania należy przekazać blok kodu jako jeden z argumentów.
Dostarczony fragment kodu musi spełniać następujący wymóg: jego sygnatura musi być identyczna z sygnaturą jedynej metody abstrakcyjnej interfejsu funkcyjnego.
Aby wyjaśnić sytuację, przyjrzyjmy się konkretnemu przykładowi:
Java ma wiele predefiniowanych interfejsów funkcyjnych, które różnią się zarówno liczbą, jak i rodzajem parametrów wejściowych i wyjściowych. Przykładem takiego interfejsu jest wspomniany wcześniej interfejs ToIntBiFunction. Podczas tworzenia własnego interfejsu funkcyjnego należy pamiętać o dodaniu adnotacji @FunctionalInterface. Adnotacja ta gwarantuje, że kompilator będzie mógł zweryfikować, czy tworzony interfejs rzeczywiście spełnia kryteria interfejsu funkcyjnego.
Interfejs funkcyjny ToIntBiFunction
- Funkcja przyjmuje dwa argumenty (T t, U u), gdzie T i U oznaczają możliwość użycia parametrów różnych typów. Na przykład mogą to być Long i String. Jednak w naszym przypadku nie jest to konieczne, ponieważ oba argumenty mają ten sam typ — int.
- Zwraca wynik jako liczbę całkowitą.
Oto, co otrzymasz:
Interfejs ToIntBiFunction
Aby zaimplementować przekazany kod w metodzie processTwoNumbers, musisz wywołać tę metodę z interfejsu funkcyjnego.
Teraz przechodzimy do tematu wyrażeń lambda.
Zrozumienie wyrażeń lambda: podstawowe koncepcje
To zwięzła notacja, inspirowana rachunkiem lambda, służąca do przekazywania fragmentów kodu jako argumentów do innego programu.
Zasadniczo jest to klasa lub metoda bez nazwy. Ponieważ w Javie wszystko, poza typami prymitywnymi, jest obiektem, wyrażenia lambda muszą być również powiązane z określonym typem obiektu. Ten typ, jak można się domyślić, nazywa się interfejsem funkcyjnym.
Zatem wyrażenie lambda nie jest niezależnym elementem, lecz służy do implementacji metody zdefiniowanej w interfejsie funkcyjnym.
Gdyby nie istniały lambdy, za każdym razem, gdy wywoływalibyśmy metodę processTwoNumbers, musielibyśmy zastosować następujące podejście:
Należy zauważyć, że w podanym przykładzie biFunction jest implementowany za pomocą klas anonimowych. W przeciwnym razie musielibyśmy utworzyć osobną klasę implementującą interfejs ToIntBiFunction i zawierającą metodę applyAsInt. Dzięki użyciu klasy anonimowej mogliśmy wykonać to zadanie szybko i sprawnie bez tworzenia dodatkowych klas.
W podanym przykładzie cały kod oprócz jednej linii jest zbędny. Tylko wyrażenie return a + b przenosi obciążenie funkcjonalne, podczas gdy reszta kodu to nadmiar techniczny. Ich liczba okazała się dość znacząca, nawet w przypadku prostej implementacji operacji dodawania dwóch liczb.
W tym miejscu z pomocą przychodzą wyrażenia lambda. Dzięki nim proces tworzenia biFunkcji jest znacznie uproszczony i można go wykonać w zaledwie dziesięciu znakach!
Wyrażenie lambda ma następującą strukturę: (argumenty) -> (ciało funkcji)
Nasza lambda będzie wyglądać następująco:
Bifunkcję przyjmującą dwie liczby całkowite i zwracającą ich sumę można zdefiniować w następujący sposób: ToIntBiFunction
W ten sposób ten zestaw 10 znaków można wykorzystać jako argument metody przyjmującej interfejs funkcyjny jako parametr. W takim przypadku z reguły nie tworzą zmiennej pośredniej i przekazują lambdę bezpośrednio.
Kompilator upewni się, że wyrażenie lambda jest zgodne z wymaganiami interfejsu funkcjonalnego, co oznacza, że akceptuje wymaganą liczbę argumentów wymaganych typów. W tym przypadku używamy interfejsu funkcjonalnego ToIntBiFunction. Jego jedyna abstrakcyjna metoda ma sygnaturę określającą dwa parametry: (Integer a, Integer b).
Na przykład, jeśli wywołasz tę metodę w ten sposób, kod nie skompiluje się, ponieważ użyty zostanie tylko jeden argument:
Istnieje wiele sposobów pisania wyrażeń lambda. Omówiliśmy tylko jeden z nich.
Używanie wyrażeń lambda w różnych obszarach.
W różnych sytuacjach. Jednym z typowych przykładów jest iteracja po elementach za pomocą pętli:
Wyrażenia lambda są również używane w komparatorach podczas implementacji sortowania. Rozważmy sytuację, w której musimy uporządkować kolekcję na podstawie ostatniej litery każdego słowa:
W większości przypadków, gdy używamy kolekcji w połączeniu z API Stream, trudno jest obejść się bez wyrażeń lambda. W poniższym przykładzie najpierw filtrujemy strumień danych na podstawie określonej wartości (używając metody filter), następnie przekształcamy każdy element (metoda map), a na koniec zbieramy wyniki do listy (metoda collect):

