jak dostać pracę jako programista artykuł
Jak dostać pierwszą pracę jako programista
17 lipca 2017
Wyrażenia lambda w języku Java logo

W artykule tym poznasz mechanizm tworzenia wyrażeń lambda. Dowiesz się jak ich używać. Poznasz też praktyczne zastosowania. Dowiesz się też jak działa operator ::. Jeśli jesteś początkującym zrozumienie wyrażeń lambda pozwoli Ci przenieść swoje umiejętności na kolejny poziom. Zdobytą wiedzę będziesz mógł przećwiczyć rozwiązując kilka przykładowych zadań.

Artykuł ten dotyczy bardziej zaawansowanego fragmentu składni języka Java. Z tego powodu aby móc w pełni skorzystać z artykułu warto zapoznać się z wcześniejszymi artykułami na temat:

Czym jest wyrażenie lambda

Dla uproszczenia można powiedzieć, że wyrażenie lambda jest metodą1. Metodą, którą możesz przypisać do zmiennej. Możesz ją także wywołać czy przekazać jako argument do innej metody.

Wyrażenia lambda możesz także porównać do klas anonimowych 2. Mają one jednak dużo bardziej czytelną i zwięzłą składnię.

Na przykład wyrażenie lambda, które podnosi do kwadratu przekazaną liczbę wygląda następująco:

Składnia wyrażeń lambda

Wyrażenie lambda ma następującą składnię

Lista parametrów

Lista parametrów zawiera wszystkie parametry przekazane do “ciała” wyrażenia lambda. W szczególności lista ta może być pusta. Wyrażenie lambda poniżej nie przyjmuje żadnych argumentów, zwraca natomiast instancję klasy String:

Podawanie typów parametrów jest opcjonalne. Kompilator jest w stanie poznać te parametry z kontekstu w którym znajduje się dane wyrażenie lambda. Jeśli chcesz możesz je także podać:

Nawiasy otaczające listę parametrów są opcjonalne jeśli wyrażenie ma wyłącznie jeden parametr bez określonego typu 3.

Ciało wyrażenia lambda

W ogromnej większości przypadków wyrażenia lambda zawierają jedną linijkę kodu:

Może się jednak zdarzyć, że Twoje wyrażenie lambda będzie zawierało więcej linii. W takim przypadku musisz otoczyć je nawiasami {} jak w przykładzie poniżej:

Można sobie wyobrazić wyrażenie lambda, które nie przyjmuje żadnych parametrów i nie zwraca żadnych wartości. Najprostsza wersja takiego wyrażenia wygląda następująco:

Od klasy anonimowej do wyrażenia lambda

Wiesz już czym jest klasa anonimowa. Dla przypomnienia powiem, że jest to stworzenie jedynej instancji klasy w miejscu jej użycia. Wiesz już też jak wyglądają wyrażenia lambda. Teraz nadszedł czas na zamianę klasy anonimowej na wyrażenie lambda. Proszę spójrz na przykład poniżej:

W przykładzie tym zdefiniowałem interfejs Checker, który posiada jedną metodę check. Metoda ta zwraca wartość logiczną na podstawie przekazanego argumentu.

Fragment kodu robiący to samo jednak przy użyciu składni wyrażeń lambda wygląda następująco:

Prawda, że ładniej :)?

Dochodzimy teraz do momentu, w którym muszę Ci powiedzieć o typach w wyrażeniach lambda. Każde wyrażenie lambda jest instancją dowolnego interfejsu funkcyjnego. Jest to bardzo ważne, dlatego też musisz dokładnie wiedzieć czym jest interfejs funkcyjny.

Interfejs funkcyjny

Interfejs funkcyjny to interfejs, który ma jedną abstrakcyjną metodę4. Wprowadzono adnotację @FunctionalInterface, którą możesz dodać do interfejsów tego typu.

Adnotacja ta zapewnia, że kompilator upewni się, że dany interfejs jest interfejsem funkcjonalnym. Jeśli nie, wówczas kompilacja się nie powiedzie.

Przykładem interfejsu funkcjonalnego może być zdefiniowany wcześniej interfejs Checker

Zawiera on wyłącznie jedną metodę check.

Przykładowe interfejsy funkcyjne

Twórcy języka Java przygotowali zestaw interfejsów funkcyjnych, które możesz implementować. W większości przypadków w zupełności wystarczy ich użycie. Część z nich znajduje się w pakiecie java.util.function. Najważniejsze z nich zebrałem poniżej:

  • Function<T, R> zawiera metodę apply, która przyjmuje instancję ,klasy T zwracając instancję klasy R
  • Consumer<T> zawiera metodę accept, która przyjmuje instancję klasy T
  • Predicate<T> zawiera metodę test, która przyjmuje instancję klasy T i zwraca flagę. Interfejs ten może posłużyć do zastąpienia interfejsu Checker.
  • Supplier<T> zawiera metodę get, która nie przyjmuje żadnych parametrów i zwraca instancję klasy T
  • UnaryOperator<T> jest specyficznym przypadkiem interfejsu Function. W tym przypadku typ argumentu i typ zwracany są te same.

Wyrażenia lambda zdefiniowane na początku artykułu można przypisać do tych właśnie interfejsów:

Zalety stosowania wyrażeń lambda

Wyrażenia lambda są bardzo pomocne przy operacji na kolekcjach. Są niezastąpione także przy pracy ze strumieniami. Pozwalają także na pisanie w Javie w sposób “funkcyjny”5.

Oczywistą zaletą wyrażeń lambda jest ich zwięzłość. Kod zajmuje o wiele mniej miejsca, staje się przez to bardziej czytelny.

Odwoływanie się do metod

Wraz z wyrażeniami lambda Java została rozbudowana o składnię pozwalającą na odwoływanie się do metod. Służy do tego ::. Dzięki temu wyrażeniu możemy przypisać metodę do zmiennej bez jej wywołania. Takie podejście pozwala na przekazanie tak wyłuskanej metody i wywołanie jej w zupełnie innym miejscu. Proszę spójrz na przykład poniżej:

W przykładzie tym tworzę nową instancję klasy Object. Następnie pobieram metodę hashCode z tego obiektu i przypisuję ją do typu IntSupplier. Jest to kolejny interfejs funkcyjny znajdujący się w standardowej bibliotece. Ostatnia linijka to wywołanie metody znajdującej się w tym interfejsie.

Kod powyżej można porównać do:

W obu przypadkach tworzę nowy obiekt klasy Object i wywołują na nim metodę hashCode.

Odwoływanie się do metod bez podania instancji

Można także odwołać się do metody bez podania instancji, na której metoda powinna być wywołana. Wówczas ta instancja musi być przekazana jako pierwszy argument. Przykład poniżej powinien pomóc zrozumieć to zastosowanie:

W odróżnieniu do poprzedniego przykładu tutaj na początku pobieram metodę. Tym razem metoda nie jest przypisana do instancji. W związku z tym wyrażenie lambda jest już innego typu. W takim przypadku zawsze pierwszym argumentem jest instancja na której metoda powinna być wywołana. W kolejnej linijce tworzę instancję klasy Object. Ostatnia linijka to wywołanie metody na tej instancji.

Kod bez użycia odwołania do metody robiący dokładnie to samo wygląda trochę mniej skomplikowanie:

Odwoływanie się do konstruktora

Notacja z :: może być także użyta do odwołania się do konstruktora. W tym przypadku należy użyć :: wraz ze słowem kluczowym new. Proszę spójrz na przykład poniżej:

W pierwszej linijce przykładu przypisuje konstruktor klasy Object do zmiennej objectCreator. Kolejna linijka to wywołanie konstruktora.

To samo bez użycia referencji metody możesz uzyskać w dobrze Ci znany sposób:

Przykład zastosowania wyrażeń lambda i odwołania do metody

Załóżmy, że chcemy wypisać na konsoli liczby znajdujące się w kolekcji. Możemy to zrobić przy pomocy standardowej pętli, którą już znasz:

To samo zadanie można także zrobić przy pomocy wyrażeń lambda:

Pierwsza linijka to utworzenie listy z liczbami. Kolejna jest bardziej ciekawa, zawiera wyrażenie lambda, które konsumuje liczbę wypisując ją na konsoli. Ostatnia to wywołanie metody forEach wraz z wyrażeniem lambda. Wyrażenie to zostanie wywołane dla każdego elementu.

Kod ten można jeszcze bardziej skrócić używając mechanizmu odwoływania się do metod:

Efekt działania wszystkich trzech fragmentów jest dokładnie taki sam. Różnią się między sobą sposobem rozwiązania danego problemu.

Zadania

Na koniec mam dla Ciebie kilka zadań, które pomogą przećwiczyć Ci wiedzę z tego artykułu.

  1. Napisz program, który pobierze o użytkownika cztery łańcuchy znaków, które umieścisz w liście. Następnie posortuj tę listę używając metody sort. Użyj wyrażenia lambda, które posortuje łańcuchy znaków malejąco po długości.
  2. Napisz program, który wywoła funkcję hashCode na instancji klasy Object używając mechanizmu odwoływania się do metody (przy pomocy ::).
  3. Utwórz instancję klasy Human przy pomocy mechanizmu odwoływania się do konstruktora (przy pomocy ::).

Jeśli będziesz miał problem z rozwiązaniem zadań możesz rzucić okiem na przykładowe rozwiązania, które umieściłem na samouczkowym githubie.

Dodatkowe materiały do nauki

Przygotowałem dla Ciebie zestaw kilku linków z materiałami dodatkowymi:

Podsumowanie

Wyrażenia lambda nie są proste. Mogą powodować sporo zakłopotania, szczególnie na początku. Jeśli jednak się do nich przyzwyczaisz pisanie kodu z ich udziałem będzie sprawiało Ci sporo frajdy :). Po pewnym czasie docenisz też zwięzłość wyrażeń lambda.

Po przeczytaniu artykułu wiesz czym są wyrażenia lambda i jak je stosować. Znasz też mechanizm odwoływania się do metod. Przećwiczyłeś te mechanizmy rozwiązując przykładowe zadania. Nie zapomnij pochwalić się w komentarzu gdzie ostatnio użyłeś wyrażeń lambda :).

Na koniec mam do Ciebie prośbę. Jeśli uważasz, że artykuł ten był dla Ciebie pomocny proszę podziel się nim ze swoimi znajomymi. Zależy mi na dotarciu do jak największej grupy czytelników a Ty możesz mi w tym pomóc. Jeśli nie chcesz pominąć żadnego nowego artykułu dopisz się do samouczkowego newslettera i polub samouczka na facebooku. Do następnego razu!

[FM_form id=”3″]

Zdjęcie dzięki uprzejmości https://www.flickr.com/photos/rofi/2097239111/sizes/l

  1. Nie jest to do końca prawda, na przykład wyrażenie lambda nie wprowadza nowego zakresu zmiennych, ale takie uproszczenie pomoże zrozumieć działanie wyrażeń lambda.
  2. Podobnie jak przy poprzednim porównaniu, są różnice pomiędzy wyrażeniami lambda i klasami anonimowymi. Jednak na potrzeby tego wprowadzenia możemy je pominąć.
  3. Oczywiście w trakcie kompilacji typ jest znany, ale nie jest jawnie podany w kodzie źródłowym.
  4. Efektywnie abstrakcyjną, czyli dodanie do interfejsu np. metody equals, która jest w klasie Object nadal spełnia to wymaganie.
  5. Oczywiście Java nie jest językiem w pełni funkcyjnym, jednak taka namiastka jest przydatna.