Lambda a vnútorné anonymné triedy
Veľmi sa nám žiada povedať, že lambda výrazy sú len skratky ako napísať vnútorné anonymné triedy. Ale pamätaj si, nie je to tak. Vyzerá to podobne ale lambda nie je implementácia rozhrania. Lambda je sama osobe nezávislá iná vec.
Pozrime sa na príklad. Namiesto toho aby sme použili implementačnú triedu nášho rozhrania IHelloWord, vytvoríme si vnútornú anonymnú triedu.
IHelloWord helloWord3 = new IHelloWord() {
@Override
public void sayHello() {
System.out.println("HelloWord impls inner anonymous class");
}
};
Všetky 3 možnosti, ktoré majú ako návratovú hodnotu rozhranie IHelloWord môžeme podsunúť do metody printHelloWord(IHelloWord helloWord).
helloWord.printHelloWord(helloWord1);
helloWord.printHelloWord(helloWord2);
helloWord.printHelloWord(helloWord3);
Ako to, ako to?
Ako java vie, aký má použiť typ pre lamba výraz? Aby sme tomu porozumeli, vytvoríme si novú triedu, kde budeme pracovať s lambda výrazom.
Vytvorme si rozhranie, ktoré bude mať jednu metódu, ktorá bude vracať int a na vstupe bude tiež int.
interface Nasob{
int nasob(int a);
}
Ako by vyerala implementácia tohto rozhrania?
class NasobPiatimi implements Nasob{
public int nasob(int a){
return a*5;
}
}
Teraz si navrhnime lambda výraz, ktorý zodpovedá danej metóde. Nepotrebujeme návratovú hodnotu int, lebo java vie na ňu prísť sama a nepotrebujeme ani názov metódy a ani modifikátor prístupu public. Náš lambda výraz bude vyzerať takto:
(int a) -> a*5;
Teraz použime tento lambda výraz:
public static void main(String[] args) {
Nasob nasobPiatimi = (int a) -> a*5;
System.out.println(nasobPiatimi.nasob(10));
}
Na výstupe bude 50. V tomto príklade sa lambda tvári ako instancia rozhrania Nasob. V predchádzajúcich príkladoch, keď sme používali HelloWord sme takúto premennú vkladali ako parameter metódy printHelloWord (HelloWord3 v IDEi). Namiesto toho sme mohli túto lambdu vložiť priamo do metódy.
helloWord.printHelloWord(() -> System.out.println("HelloWord impls lambda"););
Java kompilátor vezme tento lambda výraz a pozrie sa kam ide. Ide to metódy printHelloWord a pozrie sa, čo akceptuje na vstupe. Akceptuje rozhranie HelloWord. Ak lambda sedí s požiadavkou, že dané rozhranie obsahuje len jednu metódu a tá vracia void a na vstupe nemá žiaden parameter, tak java povie, že daná lambda je typu HelloWord. Toto sa volá Type inference. Java si sama zistí typ. Teraz, keď vieš ako java dokáže zistiť typy, vrátime sa ku príkladu, ktorý sme začali písať v tejto kapitole. V našom príklade vieme ešte viacej skrátiť zápis nášho lambda výrazu.
Nasob nasobPiatimi = (int a) -> a*5;
System.out.println(nasobPiatimi.nasob(10));
Keďže naša lamba ide do metódy rozhrania, ktorú poznáme
interface Nasob{
int nasob(int a);
}
Tak vieme presne povedať aký typ má vstupný paramter metódy. Je to int.
interface Nasob{
int nasob(int a);
}
Keď to vieme, tak nemusíme pri písaní lambda výrazu znovu špecifikovať typ vstupného parametru.
Nasob nasobPiatimi = (a) -> a*5;
A keďže máme len jeden parameter, nemusíme písať ani zátvorky.
Nasob nasobPiatimi = a -> a*5;
Už nebudeme nič mazať, lebo by nám už nič neostalo 😃
Teraz môžeme napísať metódu, ktorá bude na vstupe očakávať rozhranie Nasob a keď ju použijeme, tak do nej vložíme na vstup náš lambda výraz.
public static void printNasob(Nasob nasob){
System.out.println(nasob.nasob(10));
}
public static void main(String[] args) {
printNasob(a -> a*5);
}
V jave mohli kľudne vytvoriť nový typ pre tieto lambda výrazy. Ale nespravili to a jedným z dôvodov je aj spätná kompatibilita so starším kódom. Ako už vieme, tak lambda výrazy môžeme použiť všade tam, kde máme vyhovujúce rozhranie.
Vo vnútorných anonymných triedach, v metódach kde je na vstupe interface a podobne. Príklad:
HelloWord helloWord3 = new HelloWord() {
@Override
public void sayHello() {
System.out.println("HelloWord impls inner anonymous class");
}
};
HelloWord helloWord3 = () -> System.out.println("HelloWord impls inner anonymous class");
Pri tomto musíme pamätať, aby rozhrania boli jedno metódové alebo aby ostatné metódy rozhrania boli default. A dané metódy v rozhraniach, aby sa zhodovali s lambda výrazom. Takéto rozhranie s jednou abstraktnou metódou (metóda, ktorá poskytuje opis nie implemntáciu) sa nazýva Functional interface.
Predstav si, že používaš rozhranie, ktorý má len jednu metódu a používaš ho pre lambda výrazy. Teraz by niekto cudzí prišiel a do tohto rozhrania by pridal ďalšiu abstraktnú metódu, presnejšie jej opis bez implementácie. Takéto rozhranie by už viac nebolo functional interface a preto by sa nemohlo použiť pre lambda výraz a nastala by chyba – napriek tomu, že rozhranie by bolo v poriadku. Treba na to myslieť a ak chceme niečo pridať do functional interfac, tak len ako default metódy.
Aby sme upozornili kohokoľvek, kto by chcel niečo pridať do nášho rozhrania, tak máme možnosť pridať anotáciu @FunctionalInterface. K anotáciam sa ešte dostaneme, tak sa nebojte. Teraz je dôležité vedieť, že je to pomôcka – táto pomôcka nám spraví to, že hneď ako napíšeme ďalšiu metódu do nášho rozhrania, tak nastane chyba. Danú anotáciu nemusíme písať, ale je to super.
@FunctionalInterface
public interface HelloWord {
void sayHello();
}
Príklady na vyskúšanie:
- vytvor si zoznam miest
- zotrieď zoznam
- napíš metódu, ktorá vypíše všetko zo zoznamu miest
- urob si metódu, ktorá vypíše len tie mestá, ktoré sa skladajú z jedného slova nepoužívaj pri tom lambda výrazy
Pokračovanie nabudúce 👋
Články a online kurzy o Jave pre teba pripravuje
Jaro Beňo.