Changes

Java Funkcionális interfész & Lambda

9,938 bytes added, 15:03, 19 August 2022
Hogyan működik
=Funkcionális Interfész alapok=
... TODO...==Mire jó a funkcionális interfész==
=Lambda kifejezések=
...TODO...
A funkcionális interfészek használatával megszabadulhatunk az úgynevezett boilerplate (közhelyes) code használatától, vagyis nagyon egyszerűen, osztály példányosítás nélkül tudunk interfész metódus implementációkat készíteni. Ennek az egyik kulcs eszköze a JAVA8-ban bejött LAMBDA és úgynevezett metódus referencia, amire majd látunk bőven példát. Azt mondják hogy ez a java-ba valaha behozott legnagyobb újítás.
<br>
<br>
A fent leírtakat persze persze anonymous osztályokkal is el lehet érni, csak többet kell hozzá írni. Pl: A Theread osztályban szokásos anonymous osztályokat létrehozni a run() definiálására:
<source lang="java">
public class AnonymousClassExample {
public static void main(String[] args) {
--- Thread thread = new Thread(){ public void run(){ System.out.println("Understanding what is an anonymous class"); } }; thread.start(); }}</source><br><br> innen lefele minden törlendő ebből a fejezetből ----Lambda kifejezéssel ugyan ez így írható le: <source lang="java">public class AnonymousClassExample {
public static void main(String[] args) {
 
Runnable runnable = () -> {
System.out.println("Understanding functional interfaces");
};
runnable.run();
}
}
</source>
 
<br>
<br>
 
==Hogyan működik==
A funkcionális interfész egy olyan interfész, amiben csak egy darab absztrakt metódus van. (vagyis ami implementálandó). Ezt kidomborítandó, a '''@FunctionalInterface''' annotációval is el lehet látni az interfészt.
 
Készítsünk egy egyedi String osztályt, amivel majd példálózunk (ez még nem kapcsolódik a funkcionális if-re)
<source lang="java">
public class MyString {
 
private String input;
 
public MyString(String input) {
System.out.println("MyString consturctor is called with input:" + input);
this.input = input;
}
 
public String getInput() {
return input;
}
}
</source>
<br>
<br>
Tekintsük az alábbi funkcionális if-et, aminek egy absztrakt metódusa van, ami egy String-et vár, és egy MyString-et ad vissza.
<source lang="java">
@FunctionalInterface
public interface MyFunction {
 
MyString process(String input);
}
</source>
 
Egy funkcionális interfész implementációjának a megadására az alábbi 5 lehetőségünk van:
* Hagyományos implementációs osztály példányosítása
* anonymous osztály használata
* Lambda kifejezés használata
* Metódus referencia használata: lásd [[#Java_Method_Reference_and_Constructor_Reference]]
* Konstruktor referencia használata lásd [[#Java_Method_Reference_and_Constructor_Reference]]
<br>
<br>
===Implementációs osztály példányosítása===
A klasszikus használata egy interfésznek, ha készítünk egy osztályt, ami implementálja az absztrakt metódusokat, majd példányosítjuk az osztályt, és klasszikus módon használjuk. Ehhez nincs szükség funkcionális IF-re, vagyis hogy csak egy absztrakt metódusa legyen az interfésznek, mivel az implementációban explicit megmondjuk, hogy melyiket implementáljuk:
<source lang="java">
public class MyFunctionImpl implements MyFunction {
@Override
public MyString process(String input) {
return new MyString(input);
}
}
</source>
<br>
Majd ezt így használhattuk:
<source lang="java">
MyFunctionImpl2 myFunctionImpl2 = new MyFunctionImpl2();
MyString myString = myFunctionImpl2.process("MyInput");
System.out.println(myString.getInput());
</source>
<br>
<br>
===Anonymous osztály használata===
Ahogy a bevezetőben is láttuk, bármilyen interfésznek lehet anonymous osztállyal definiálni a még nem definiált interfészeit az alábbi szintaxissal:
<pre>
new InterfaceName() {
@Override
public <implementálandó metódus definíció> {..}
};
</pre>
Itt sincs szükség funkcionális interfészre, vagyis hogy csak egy absztrakt metódusa legyen az interfésznek, mert explicit megmondjuk hogy melyiket implementáljuk.
<br>
Gyakorlatban ez így néz ki az előbbi példára:
<source lang="java">
MyFunction fm2 = new MyFunction() {
@Override
public MyString process(String input) {
return new MyString(input);
}
};
fm2.process("input");
 
</source>
 
<br>
<br>
===Lambada kifejezés használata===
A fenti anonymous osztály definíció egy sokkal rövidebb Lambda kifejezéssel kiváltható, de csak akkor ha a szóban forgó interfész egy '''funkcionális interfész''', vagyis pontosan egy darab absztrakt metódusa van. A Lambda kifejezés szintaxisa az alábbi:
<pre>
(arguments) -> {function body}
</pre>
Ahol az argumentumok az egy szem absztrakt metódus input paraméterei, a function bdoy-ban pedig leírjuk, hogy mit tegyen ezekkel az input paraméterekkel az implementáció. Az argumentumok típus nélküliek, a típusuk implicit következik a felhasználási módból, lásd lentebb.
* A paraméterek neve tetszőleges lehet, nem kell megegyezzen a funkcionális metódus paraméter neveivel. A sorrend viszont számít. A lényeg, hogy a body-ban az argumentumokban felsorolt nevekkel tudunk hivatkozni az input paraméterekre.
* Ha az interfész input paraméter generikus, csak akkor fognak típust kapni, amikor a generikus értékeket definiáljuk az interfész változó létrehozásánál.
* Ha a visszatérési érték is generikus az implementálandó metódusban, akkor annak a típusa is csak az adott felhasználásból fog következni.
* Ha a funkcionális interfész nem generikus, akkor az interfész input és return értékéből már explicit következik a lambdában használt paraméterek és visszatérési értékék típusa.
 
<br>
<br>
Nézzünk egy konkrét példát. A 'MyFunction' egy funkcionális interfész, mert pontosan egy absztrakt metódusa van: 'MyString process(String)'. Tehát a lambda kifejezéssel ennek a 'process(..)' absztrakt metódusnak az implementációját kell definiálni:
<source lang="java">
MyFunction fm = var1 -> {
System.out.println("inside lambda body");
return new MyString(var1);
};
System.out.println("Start");
fm.process("input");
System.out.println("End");
</source>
Láthatjuk, hogy a 'MyFunction' egy szem absztrakt metódusát egy lambda kifejezéssel definiáltuk, amivel egyben el is készítettük a MyFunction implementációjának példányosítását. A 'MyFunction'-enk egy darab absztrakt metódusa van, az alábbi szignatúrával: 'MyString process(String)', ebből következik, hogy a '->' elé pontosan egy változó nevet kell kitaláljunk, ami bármi lehet, lényeg, hogy a body-ban ezzel a névvel tudunk rá hivatkozni. Mi itt 'var1' nevet találtunk ki, aminek a típusa 'String', mivel a 'process(..) metódusban egy darab String input paraméter van. Ha a 'process(..)' generikus inputtal rendelkezne, akkor a lambada-ban a 'var1' nek a típusa csak használat közben derülne ki, majd később erre is látunk példát.
<br>
Fontos, hogy a 'var1'-nek a globálisan semmi jelentése nincs, nem létező változó a fenti kódot futtató kontextusban. Ezt úgy kell elképzelni, mint egy metódus leírója, ami majd akkor kerül meghívásra csak, ha meghíjuk programozottan a 'process(..)' metódust, csak akkor fog feltöltődni értékekkel. <br>
Az stdout-ra a következő kerül:
<pre>
Start
inside lambda body
End
</pre>
 
<br>
<br>
 
Tehát az alábbi két sor egyenlő:
<source lang="java">
MyFunction fm = var1 -> { return new MyString(var1); };
fm.process("input");
EGYENLŐ
MyFunction fm = new MyFunctionImpl();
fm.process("input");
</source>
 
!!!Mit fontos itt látni: A lambda definiálásának a sorába még nem fut le a lambda törzsében megadott kód, vagyis ez: return new MyString(var1); !!!
 
 
<br>
<br>
===Lambda generikus interfésszel===
Tegyük fel, hogy adott az alábbi generikus, funkcionális interfész. A funkcionális (absztrakt) metódusa egy T-t és H-t vár és R-t gyárt belőle. Azt hogy hogyan, és hogy mi a T, H és mi az R, az implementációra van bízva.
<source lang="java">
@FunctionalInterface
public interface MyFunctionGeneric<R, T, H> {
R doIt(T var, H var2);
}
 
</source>
<br>
Definiáljuk a 'MyFunctionGeneric' interfész absztrakt metódusát egy lambda kifejezéssel. A 'MyFunctionGeneric fm' változó létrehozásakor már definiálni kell az R és T és H típusokat, ebből fog következni, hogy a lambada-ban használt paraméter listának milyen értékei vannak, hogy mi kell legyen a visszatérési érték.
<br>
<br>
Az alábbi példában létrehoztuk az fm3 'MyFunctionGeneric' változót, és itt meghatároztuk, hogy a visszatérési érték Integer, és a bement két String. A lambda kifejezés input paramétereiben már két értéket kell megadni, ezért zárójelet kell alkalmazni.
<source lang="java">
MyFunctionGeneric<Integer, String, String> fm3 = (myInput1, myInput2) -> myInput1.length();
fm3.doIt("my input", "my input2");
</source>
Az input argumentumoknak megint csak bármilyen nevet megadhatunk, a fenti kódot futtató kontextusban nem léteznek, ez csak egy metódus tervrajz, nem metódus futtatás. A 'myInput1' és 2-nek a típusa következik az fm3-ban megadott generikus típusokból, vagyis mind a kettő String. A visszatérési érték pedig Integer. Ha a lambda body-ban nem akarunk több soros művelet végrehajtani, csak egy 'return (one line logic)' sort tartalmazna, akkor a 'return' kulcsszó elhagyható. Fontos megint csak látni, hogy a doIt(..) metódus hívás hatására fog csak lefutni a lambda-ban definiált kód.
<br>
<br>
 
=Lambda használati példák =
==Tipikus használati minták==
 
..TODO: mindig egy metódus beljesében van, és a metódus lambda-t vár input paraméterben, és az osztály belsejében lévő változókkal szokott operálni. ...
 
<br><br>
==List-forEach()==
<br>