Blog

Lambda výrazy v Jave - časť IV.

Learn2Code tím - 30.10.2019 - Tipy a triky

Funkcionálne rozhrania

Ak chcem používať lambda výraz, tak potrebujem na to rozhranie s jednou abstraktnou metódou. Daná metóda musí odpovedať popisu nášho lambda výrazu.

Ak sa nad tým zamyslíš, tak v skutočnosti sa dané rozhranie môže volať hocijako. Na názve nezáleží. A aj metóda v tom rozhraní môže mať hocijaký názov, pre logiku lambda výrazu to nemá žiaden zmysel. Jediné, čo je dôležité je, aby metóda sedela s lamba výrazom v tom, čo vracia a to, čo je na vstupe metódy ako parameter.

Bolo by úplne super, keby sme nemuseli vždy pri písaní lambda výrazu riešiť vytvorenie nového rozhrania, ktoré nám bude slúžiť ako typ daného lambda výrazu. Čo povieš?

Povedali sme, si, že java nevytvorila nový typ pre lambdy. Pri písaní, sme si mohli všimnúť, že metódy sú často podobné. Vraciam nejaký typ alebo vraciam void a mám tam názov metódy tam sú alebo nie sú parametre. Sú tu nejaké paterny, nejaké vzorce, ktoré sa opakujú častejšie.

Java nám ponúka niekoľko takých rozhraní, ktoré môžeme kľudne použiť. Tieto rozhrania sú v balíčku java.util.function. V tomto balíku je mnoho pred pripravených rozhraní, ktoré môžeš používať. Tieto rozhrania používajú generiká, tak si tam vieš dosadiť objekty aké potrebuješ.

Napríklad Predicate je presne stvorený na to, ak potrebujeme zobrať na vstupe objekt a vrátiť boolean ako návratovú hodnotu. Takto môžeme použiť toto rozhranie namiesto toho rozhrania, čo sme si sami napísali, keď sme riešili predchádzajúcu úlohu.

Ako ošetriť výnimky

Spravme si príklad, ktorý bude obsahovať zoznam osôb, ktoré budem spracovávať – vypíšeme ich mená a dáme ich na veľké písmená.

public class ExceptionHandling {
    public static void main(String[] args) {
        ArrayList<Osoba> osoby = new ArrayList<>();
        osoby.add(new Osoba("jano", "beno", 3));
        osoby.add(new Osoba("peter", "beno", 0));
        osoby.add(new Osoba("jaro", "beno", 30));
        osoby.add(new Osoba("brano", "beno", 28));
        processOsoby(osoby);
    }
    private static void processOsoby(ArrayList<Osoba> osoby) {
        for (Osoba osoba : osoby){
            System.out.println(osoba.getMeno().toUpperCase());
        }
    }
}


Prepíšeme si to na lambda výraz. Náš kód, ktorý chceme metóde predať ako argument je System.out.println(osoba.getMeno().toUpperCase()). Pracujem teda len s objektom osoba. Výsledok napíšem na konzolu. Tým pádom mám jeden argument a tento kus kódu nevracia žiadnu hodnotu. Budeme na to potrebovať funkcionálne rozhranie, ktoré má metódu s jedným parametrom a nevracia nič. Takým je Consumer s jeho metódou accept.

public class ExceptionHandling {
    public static void main(String[] args) {
        ArrayList<Osoba> osoby = new ArrayList<>();
        osoby.add(new Osoba("jano", "beno", 3));
        osoby.add(new Osoba("peter", "beno", 0));
        osoby.add(new Osoba("jaro", "beno", 30));
        osoby.add(new Osoba("brano", "beno", 28));
        processOsoby(osoby, osoba -> System.out.println(osoba.getMeno().toUpperCase()));
    }
    private static void processOsoby(ArrayList<Osoba> osoby, Consumer<Osoba> consumer) {
        for (Osoba osoba : osoby){
            consumer.accept(osoba);
        }
    }
}


Teraz si náš zoznam osôb zmením tak, že namiesto mien dám do zoznamu null. Pri spracúvaní lamba výrazu nám program spadne na NullPointerException.

osoby.add(new Osoba("jano", "beno", 3));
osoby.add(new Osoba(null, "beno", 0));
osoby.add(new Osoba("jaro", "beno", 30));
osoby.add(new Osoba("brano", "beno", 28));

Musíme si ošetriť túto výnimku. Ako na to? Jedným zo spôsobov je obaliť volanie consumer.accept do try catch bloku.

private static void processOsoby(ArrayList<Osoba> osoby, Consumer<Osoba> consumer) {
    for (Osoba osoba : osoby){
        try {
            consumer.accept(osoba);
        }catch (NullPointerException e){
            //...
        }
    }
}

Ale to je škaredé riešenie. To čo príde do consumer môže byť všeličo možné a nemusí to dať NullPointerException, možno to bude iná výnimka. Náš kód chceme mať jednoduchší. Druhou možnosťou je, aby bola výnimka spracovaná priamo v lamba výraze.

public class ExceptionHandling {
    public static void main(String[] args) {
        ArrayList<Osoba> osoby = new ArrayList<>();
        osoby.add(new Osoba("jano", "beno", 3));
        osoby.add(new Osoba(null, "beno", 0));
        osoby.add(new Osoba("jaro", "beno", 30));
        osoby.add(new Osoba("brano", "beno", 28));
        processOsoby(osoby, osoba -> {
            try {
                System.out.println(osoba.getMeno().toUpperCase());
            }catch (NullPointerException e){
                e.printStackTrace();
            }
        });
    }
    private static void processOsoby(ArrayList<Osoba> osoby, Consumer<Osoba> consumer) {
        for (Osoba osoba : osoby){
            consumer.accept(osoba);
        }
    }
}

Dosiahol som to, že metóda processOsoby je krajšia, ale náš lambda výraz je teraz viacriadkový a nie pekný - jednoriadkový.

Na jednej strane chceme mať pekné jednoduché lambda výrazy, na druhej strane chceme, aby bolo postarané o výnimky.

V našom kóde sa vráťme k riešeniu, ktoré nepoužíva try catch blok. Na odchytenie výnimky použijeme wrapper metódu. Try catch blok si vyvedieme do osobitnej metódy a potom obalíme náš lambda výraz, ďalším lambda výrazom, ktorý má try catch blok. Urobme to, čo som teraz napísal.

Vytvoríme si novú metódu, ktorá bude akceptovať lambda výraz. V našom prípade sme na to použili Consumer rozhranie. A keďže je to wrapper, tak to čo mi príde na vstup tak dám aj na výstup.

private static Consumer<Osoba> wrapperLambda(Consumer<Osoba> consumer){
    return consumer;
}

V metóde processOsoby(osoby, osoba -> System.out.println(osoba.getMeno().toUpperCase())); zavolám namiesto lambda výrazu, wrapper metódu, ktorej argument bude lambda výraz. Urobí to to isté, ale použil som wrapper metódu.

public class ExceptionHandling {
    public static void main(String[] args) {
        ArrayList<Osoba> osoby = new ArrayList<>();
        osoby.add(new Osoba("jano", "beno", 3));
        osoby.add(new Osoba("peter", "beno", 0));
        osoby.add(new Osoba("jaro", "beno", 30));
        osoby.add(new Osoba("brano", "beno", 28));
        processOsoby(osoby, wrapperLambda(osoba -> System.out.println(osoba.getMeno().toUpperCase())));
    }
    private static void processOsoby(ArrayList<Osoba> osoby, Consumer<Osoba> consumer) {
        for (Osoba osoba : osoby){
            consumer.accept(osoba);
        }
    }
    private static Consumer<Osoba> wrapperLambda(Consumer<Osoba> consumer){
        return consumer;
    }
}

Tu môžem spraviť nasledujúcu vec. Namiesto toho aby som lambdu prehnal cez wrapper metódu, tak ju ani nepoužijem, ale použjime len jej vstupný parameter, čo je osoba. Môžem spraviť niečo takéto:

private static Consumer<Osoba> wrapperLambda(Consumer<Osoba> consumer){
    return osoba -> System.out.println(osoba.getPriezvisko());
}

Namiesto toho, aby som využil vstupnú lambdu, ktorá mi prišla cez parameter consumer, som na ňu zabudol a len som využil vstupný parameter danej lambdy a vytvoril som novú lambdu.

Pri volaní consumer.accept(osoba); v metóde processOsoby sa vykoná lambda výraz z wrapper metódy. Toto nie je skutočný wrapper. Skutočný wrapper, zoberie vstupnú lambdu a vykoná čo požaduje. Teraz máme istotu, že sa zavolá presne náš požadovaný lambda výraz a zároveň môžeme pridávať kód okolo.

private static Consumer<Osoba> wrapperLambda(Consumer<Osoba> consumer){
    return osoba -> consumer.accept(osoba);
}

Tu prichádza narad try catch blok v wrapper metóde. Upravíme si kód, aby nám hádzal výnimku.

public class ExceptionHandling {
    public static void main(String[] args) {
        ArrayList<Osoba> osoby = new ArrayList<>();
        osoby.add(new Osoba("jano", "beno", 3));
        osoby.add(new Osoba(null, "beno", 0));
        osoby.add(new Osoba("jaro", "beno", 30));
        osoby.add(new Osoba("brano", "beno", 28));
        processOsoby(osoby, wrapperLambda(osoba -> System.out.println(osoba.getMeno().toUpperCase())));
    }
    private static void processOsoby(ArrayList<Osoba> osoby, Consumer<Osoba> consumer) {
        for (Osoba osoba : osoby){
            consumer.accept(osoba);
        }
    }
    private static Consumer<Osoba> wrapperLambda(Consumer<Osoba> consumer){
        return osoba -> {
            try{
                consumer.accept(osoba);
            }catch (NullPointerException e){
                System.out.println("Null pointer exception in wrapper lambda");
            }
        };
    }
}

Ak sa zastavuješ pri myšlienke, že sme nič nezjednodušili, len sme presunuli kód na iné miesto, tak máš pravdu, ale! Ak si danú metódu spravíš generickú, tak si do tejto metódy môžeš zabaliť hocijakú lambdu, ktorej typ je Consumer rozhranie. Škoda, že tvorcovia javy nespravili takéto wrapper metódy pre všetky funkcionálne rozhrania z balíku java.util.function.

private static<T> Consumer<T> wrapperLambda(Consumer<T> consumer){
    return osoba -> {
        try{
            consumer.accept(osoba);
        }catch (NullPointerException e){
            System.out.println("Null pointer exception in wrapper lambda");
        }
    };
}


Pokračovať s Lambda výrazmi budeme opäť v ďalšom článku. Moje meno je Jaro Beňo a naučím ťa programovať v Jave. Ahoj.


Logo facebook

Learn2Code tím

Učíme ľudí dizajnovať, robiť webstránky a programovať. Naše prezenčné kurzy nájdeš vo viacerých mestách na Slovensku a pomocou online kurzov sa môžeš vzdelávať z pohodlia domova.


Ako zistiť, či je číslo zadané zo vstupu prvočíslom?

Tipy a triky

V 15. kapitole online kurzu vyššieho programovacieho jazyka C++ úrovne Elementary II nájdete medzi zadaniami praktických príkladov na domáce precvičenie...

Lambda výrazy v Jave - časť III.

Tipy a triky

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...

Lambda výrazy v Jave - časť II.

Tipy a triky

Typy lambda výrazov Tento článok je pokračovaním prvej časti tutoriálu o Lambda výrazoch. Vráťme sa k nášmu príkladu na začiatku, kde sme do metódy...