Wybierz region
pl
  • PL
  • EN
Wydrukuj

Cykl: Podstawy Javy. Epizod 6: Wyrażenia lambda.

W ostatnim wpisie z serii obejmującej podstawy Javy przybliżyliśmy sobie nieco zagadnienie interfejsów, pokazując m.in., w jaki sposób pozwalają one na uproszczenie oraz skrócenie kodu. W dzisiejszym wpisie zajmiemy się elementem języka Java, który pozwala uczynić kod jeszcze bardziej czytelnym i zwięzłym.

Elementem tym są wyrażenia lambda.

W pierwszej kolejności stwórzmy następujący interfejs:

public interface SpacesRemover {

String removeSurplusSpaces(String target);

}

Interfejs tego rodzaju, posiadający tylko jedną metodę określa się mianem interfejsu funkcyjnego. W powyższym przypadku metoda zdefiniowana w interfejsie odpowiadać ma za usuwanie nadmiarowych spacji ze Stringa przekazanego jako argument.

Najprostsza implementacja interfejsu wyglądać mogłaby w następujący sposób:

String[] sentences = {" Ala     ma kota.  ", "   Kot ma    Alę?   ", "     Oto   dom. ", " Co   to? "};

 

class SpacesRemoverImpl implements SpacesRemover {

@Override

public String removeSurplusSpaces(String target) {

return target.trim().replaceAll(" +", " ");

}

}

 

SpacesRemoverImpl spacesRemover = new SpacesRemoverImpl();

 

for (int i = 0; i < sentences.length; i++) {

sentences[i] = spacesRemover.removeSurplusSpaces(sentences[i]);

}

Jak widać, zaimplementowaliśmy interfejs funkcyjny (w klasie SpacesRemoverImpl) i w pętli for przetworzyliśmy każde zdanie zawierające zbędne spacje. Rozwiązanie to wymagało napisania sporej liczby linii kodu. Ponadto, jeżeli potrzebowalibyśmy po raz kolejny przetworzyć całą tablicę Stringów, to każde takie kolejne przewożenie wymagałoby ponawiania pętli for, czyli generowania kolejnych linii kodu.

Opisany problem moglibyśmy częściowo wyeliminować, zamykając pętlę for widoczną powyżej w pomocniczej metodzie, która mogłaby wyglądać następująco:

static void processSentences(String[] sentences, SpacesRemover remover) {

for (int i = 0; i < sentences.length; i++) {

sentences[i] = remover.removeSurplusSpaces(sentences[i]);

}

}

Jak widzimy, metoda ta jako pierwszy argument przyjmuje tablicę Stringów, którą chcemy przetworzyć, jako drugi zaś – obiekt klasy rozszerzającej interfejs SpaceRemover (możemy przekazać taki obiekt jako odrębny byt, ale także zaimplementować interfejs ad hoc, jako klasę anonimową). Warto zwrócić uwagę, że interfejs SpacesRemover posiada tylko jedną metodę, którą musimy zaimplementować i którą chcemy zastosować wewnątrz metody pomocniczej na każdym elemencie tablicy. Dużym uproszczeniem byłoby zatem, gdybyśmy nie musieli tworzyć obiektu klasy implementującej interfejs SpacesRemover, ale mogli przekazać jako argument bezpośrednio metodę, którą chcemy wywołać na każdym elemencie tablicy. Taką możliwość dają nam wyrażenia lambda. Stosując takie wyrażenie, moglibyśmy wywołać powyższą metodę w następujący sposób:

processSentences(sentences, sentence -> sentence.trim().replaceAll("  +", " "));
Jako drugi argument wywołania metody przekazane zostało wyrażenie lambda. Choć oczywiście nie można utożsamiać takiego wyrażenia z metodą, to efekt przekazania wyrażenia lambda jako argumentu metody jest taki sam, jaki byłby wówczas, gdybyśmy mogli przekazać normalną metodę. Równocześnie jest to bez wątpienia rozwiązanie bardziej zwięzłe i eleganckie, niż tworzenie w tym miejscu klasy anonimowej. Dodatkowo pozwala ono na oszczędzenia zasobów komputera, ponieważ w przeciwieństwie do klas anonimowych, wyrażenia lambda nie muszą być kompilowane do plików .class. 

Ogólna składnia wyrażeń lambda w Javie przedstawia się następująco:

(argumenty) -> ciało funkcji

W naszym przypadku do wyrażenia przekazaliśmy jeden argument, ale możliwe jest także przekazanie większej ich liczby. W takim przypadku argumenty umieszczone muszą być w nawiasie okrągłym i oddzielone przecinkami. Dodatkowo możliwe jest zamknięcie ciała funkcji w nawiasach klamrowych, co pozwala na umieszczenie tam nawet bardzo rozbudowanej logiki. Dopuszczalne jest także jawne podawanie typów argumentów wyrażenia lambda (nie jest to konieczne, jako że Java jest językiem silnie typowanym, więc typ zmiennej i tak musi być już znany przy kompilacji, a zatem kompilator wie, jakiego typu jest argument wyrażenia nawet, jeśli go jawnie nie wskażemy) oraz umieszczanie w ciele wyrażenia słowa ‘return’.

Przestawione na wcześniejszym listingu wyrażenie mogłoby zatem wyglądać również następująco:

(String sentence) -> {return sentence.trim().replaceAll("  +", " ");}
Na podstawie powyższych przykładów dostrzec można, że zaprezentowane wyrażenie lambda spełnia podobną rolę, jak implementacja interfejsu SpacesRemover. Stanowi ono substytut klasy jawnie rozszerzającej ten interfejs lub klasy anonimowej. Zarówno lambda, jak i implementacja interfejsu posiadają argumenty oraz ciało, w którym zawarta jest logika przetwarzania tych argumentów. Składnia wyrażeń lambda jest przy tym znacznie bardziej zwięzła niż składnia klas.

W dotychczasowych rozważaniach wykazaliśmy, że wyrażenia lambda mogą być przekazywane jako argumenty wywołania metod analogicznie jak zmienne. Wyrażenia takie można także przypisywać bezpośrednio do zmiennych typu interfejsu funkcyjnego, np. w ten sposób:

SpacesRemover remover = sentence -> sentence.trim().replaceAll("  +", " ");
Podobnie jak we wcześniejszych przykładach, taka składnia wydaje się bardziej zwięzła i czytelna niż klasa anonimowa.

W dzisiejszym wpisie przedstawiona została istota wyrażeń lambda oraz zaprezentowane zostały przykłady pokazujące, o ile bardziej czytelny staje się dzięki nim kod. Do wyrażeń lambda wrócimy jeszcze w kolejnych wpisach, po zapoznaniu się ze strumieniami w Javie, ponieważ to właśnie w kontekście strumieni najbardziej ujawniają się ich ogromne możliwości.

 

 


Michał Karmelita

Z wykształcenia prawnik i informatyk. Zawodowo i z zamiłowania programista Java. W wolnym czasie stara się oddawać przyjemnościom i unikać przykrości. Zapalony podróżnik.


Wydrukuj