Wyrażenie lambda
Wyrażenia lambda (ang. lambda expression) są próbą zaimplementowania koncepcji związanych z programowaniem funkcyjnych* w pełni obiektowym języku jakim jest Java. Koncept ten jest bardzo stary, o czym możesz się przekonać czytając chociażby blog Martina Fowlera**. Dotychczas w Javie pewną brzydką alternatywą dla wyrażeń lambda były klasy anonimowe. Sam przyznasz jednak, że nie są one zbyt eleganckim rozwiązaniem.
Wyrażenia lambda wyglądają dużo lepiej i poprawiają czytelność kodu. Kolejną przewagą wyrażeń lambda, jest to, że obliczenia zawarte w nich nie wychodzą poza blok funkcji, co rozwiązuje wiele problemów w programowaniu obiektowym, związanych z kontrolą stanu obiektu***. Przypominają one też funkcje matematyczne typu f(x)=x+1, z tą różnicą, że w tym przypadku, masz też możliwość używania takiej funkcji nie podając żadnego argumentu i de facto nic nie zwracając.
public class LambdaTest { public static void main(String[] args) { Playable anonymousClass = new Playable() { @Override public void play() { System.out.println("JDK 7"); } }; Playable lambdaExpression = () -> System.out.println("JDK 8"); anonymousClass.play(); lambdaExpression.play(); } }
Interfejs funkcyjny
Aby móc skorzystać z wyrażenia lambda, należy wpierw zaimplementować z tzw. interfejs funkcyjny. Jego idea jest bardzo prosta, jest to zwykły interfejs tylko, że z jedną metodą abstrakcyjną (sygnaturą). Ważne podkreślenia jest też to, że Twój interfejs funkcyjny może mieć dowolną liczbę metod statycznych i domyślnych. Dodatkowo warto skorzystać z adnotacji @FunctionalInterface, która ostrzeże Cię przed skompilowaniem kodu, gdy Twój interfejs nie stosuje się do zasad opisanych wcześniej.
Przykładowy interfejs funkcyjny:
@FunctionalInterface public interface Playable { public void play(); //public void rewind(); can't add more than one signature to functional interface }
Klasa anonimowa vs. interfejs funkcyjny
W przypadku powyżej, dwukrotnie skorzystałem z interfejsu Playable, raz implementując go w formie klasy anonimowej, drugi raz jako wyrażenie lambda. Wydaje się, że obie formy różni tylko zapis. To jednak nie prawda. Postaraj się sprawdzić poniższy zapis:
Object playable = new Playable() { @Override public void play() { System.out.println("JDK 7"); } }; Object lambda = () -> { System.out.println("JDK 8"); }; //error, lambda can't be an object, until it gets cast to specific interface Object lambdaAfterCasting = (Playable) () -> { System.out.println("JDK 8"); };
Wyrażenia lambda i klasa Object
Dotąd mówiłem, że wszystko w Javie (poza typami prostymi) jest obiektem. Okazuje się, że od JDK w wersji 8, nie jest tak do końca. Lambda jest wyjątkiem i może być przypisana tylko i wyłącznie do konkretnego interfejsu funkcyjnego. W tej lekcji pokazałem Ci jedynie najprostszą z możliwych lambd do napisania, ale się domyślasz nie stoi nic na przeszkodzie, aby napisać bardziej skomplikowany interfejs funkcyjny.
@FunctionalInterface public interface Drivable { public int accelerate(int force); }
Drivable driver = x -> { System.out.println(x); return x*x; }; int force = 5; System.out.println(driver.accelerate(force));
Inne właściwości wyrażenia lambda
Taka lambda nie tylko przyjmuje argument, ale także zwraca wartość. Co więcej ma więcej niż jedną linię kodu. Możesz też używać lambd, w przypadku natywnych funkcji JDK, które spełniają warunki bycie interfejsem funkcyjnych (np. w przypadku Runnable). Więcej wariacji przeróżnych interfejsów funkcyjnych opiszę Ci w następnej lekcji.
*Więcej o programowaniu funkcyjnym: https://en.wikipedia.org/wiki/Functional_programming
**Link do notki Martina Fowlera z 2004 roku (!) odnośnie wyrażeń lambda: https://martinfowler.com/bliki/Lambda.html
***Przeczytaj o tzw. side effects w programowaniu: https://www.radford.edu/nokie/classes/320/Tour/side.effects.html
Kod do lekcji: https://github.com/developeronthego/java-jdk8/tree/master/src/main/java/java8/lambda
Czym jest klasa anonimowa, znajdziesz u mnie w lekcji: Java #44: klasy wewnętrzne