Funkcionální rozhraní
Pokud chci používat lambda výraz, tak potřebuji k tomu rozhraní s jednou abstraktní metodou. Daná metoda musí odpovídat popisu našeho lambda výrazu.
Pokud se nad tím zamyslíš, tak ve skutečnosti se dané rozhraní může jmenovat ledajak. Na názvu nezáleží. A i metoda v tom rozhraní může mít ledajaký název, pro logiku lambda výrazu to nemá žádný smysl. Jediné, co je důležité je, aby metoda seděla s lamba výrazem v tom, co vrací a to, co je na vstupu metody jako parametr.
Bylo by naprosto super, kdybychom nemuseli vždy při psaní lambda výrazu řešit vytvoření nového rozhraní, které nám bude sloužit jako typ daného lambda výrazu. Co řekneš?
Řekli jsme si, že java nevytvořila nový typ pro lambdy. Při psaní, jsme si mohli všimnout, že metody jsou často podobné. Vracím nějaký typ nebo vracím void a mám tam název metody tam jsou nebo nejsou parametry. Jsou zde nějaké paterny, nějaké vzorce, které se opakují častěji.
Java nám nabízí několik takových rozhraní, která můžeme klidně použít. Tato rozhraní jsou v balíčku java.util.function. V tomto balíčku je mnoho před připravených rozhraní, které můžeš používat. Tato rozhraní používají generika, tak si tam umíš dosadit objekty jaké potřebuješ.
Například Predicate je přesně stvořený k tomu, pokud potřebujeme vzít na vstupu objekt a vrátit boolean jako návratovou hodnotu. Takto můžeme použít toto rozhraní namísto toho rozhraní, co jsme si sami napsali, když jsme
řešili předchádzající úlohu.
Jak ošetřit výjimky
Udělejme si příklad, který bude obsahovat seznam osob, které budu zpracovávat – vypíšeme jejich jména a dáme je na velká písmena.
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());
}
}
}
Přepíšeme si to na lambda výraz. Náš kód, který chceme metodě prodat jako argument je System.out.println(osoba.getMeno().toUpperCase()). Pracuji tedy jen s objektem osoba. Výsledek napíšu na konzoli. Tím pádem mám jeden argument a tento kus kódu nevrací žádnou hodnotu. Budeme k tomu potřebovat funkcionální rozhraní, které má metodu s jedním parametrem a nevrací nic. Takovým je Consumer s jeho metodou 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);
}
}
}
Nyní si náš seznam osob změním tak, že místo jmen dám do seznamu null. Při zpracovávání 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šetřit tuto výjimku. Jak na to? Jedním ze způsobů je obalit volání 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 ošklivé řešení. To co přijde do consumer může být leccos možné a nemusí to dát NullPointerException, možná to bude jiná výjimka. Náš kód chceme mít jednodušší. Druhou možností je, aby byla výjimka zpracována přímo v lamba výrazu.
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);
}
}
}
Dosáhl jsem toho, že metoda processOsoby je krásnější, ale náš lambda výraz je nyní víceřádkový a ne pěkný - jednořádkový.
Na jedné straně chceme mít pěkné jednoduché lambda výrazy, na druhé straně chceme, aby bylo postaráno o výjimky.
V našem kódu se vraťme k řešení, které nepoužívá try catch blok. K odchycení výjimky použijeme wrapper metodu. Try catch blok si vyvedeme do zvláštní metody a poté obalíme náš lambda výraz, dalším lambda výrazem, který má try catch blok. Udělejme to, co jsem teď napsal.
Vytvoříme novou metodu, která bude akceptovat lambda výraz. V našem případě jsme k tomu použili Consumer rozhraní. A protože je to wrapper, tak to co mi přijde na vstup tak dám i na výstup.
private static Consumer<Osoba> wrapperLambda(Consumer<Osoba> consumer){
return consumer;
}
V metodě processOsoby(osoby, osoba -> System.out.println(osoba.getMeno().toUpperCase())); zavolám místo lambda výrazu, wrapper metodu, jejíž argument bude lambda výraz. Udělá to totéž, ale použil jsem wrapper metodu.
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;
}
}
Tady můžu udělat následující věc. Namísto toho abych lambdu přehnal přes wrapper metodu, tak ji ani nepoužiji, ale použijeme jen její vstupní parametr, což je osoba. Mohu udělat něco takového:
private static Consumer<Osoba> wrapperLambda(Consumer<Osoba> consumer){
return osoba -> System.out.println(osoba.getPriezvisko());
}
Namísto toho, abych využil vstupní lambdu, která mi přišla přes parametr consumer, jsem na ni zapomněl a jen jsem využil vstupní parametr dané lambdy a vytvořil jsem novou lambdu.
Při volání consumer.accept(osoba); v metodě processOsoby se provede lambda výraz z wrapper metody. Toto není skutečný wrapper. Skutečný wrapper, vezme vstupní lambdu a provede co požaduje. Nyní máme jistotu, že se zavolá přesně náš požadovaný lambda výraz a zároveň můžeme přidávat kód kolem.
private static Consumer<Osoba> wrapperLambda(Consumer<Osoba> consumer){
return osoba -> consumer.accept(osoba);
}
Zde přichází nářad try catch blok v wrapper metodě. Upravíme si kód, aby nám házel výjimku.
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");
}
};
}
}
Pokud se zastavuješ při myšlence, že jsme nic nezjednodušili, jen jsme přesunuli kód na jiné místo, tak máš pravdu, ale! Pokud si danou metodu uděláš generickou, tak si do této metody můžeš zabalit ledajakou lambdu, jejíž typ je Consumer rozhraní. Škoda, že tvůrci jevy neudělali takové wrapper metody pro všechna funkcionální rozhraní 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");
}
};
}