Changes

Java Funkcionális interfész & Lambda

18,640 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>
Bármi is legyen az implementáció (impl osztállyal vagy inline lambda kifejezéssel) a lényeg, hogy előállítson a MyString objektumot. <br>
A konstruktor referencia lehetővé teszi, hogy a funkcionális IF implementációt egy osztály konstruktorával helyettesítsük be. Vagyis a funkcionális metódus visszatérési objektumát úgy állíts állítja elő, hogy szimplán példányosítsd példányosítja a megadott osztályt, jelen esetben a MyString-et. Az osztálynak rendelkeznie kell egy olyan konstruktorral, ami a funkcionális metódus input paramétereivel megegyezik. A fenti esetben tehát akkor fogjuk tudni a MyString -et konstruktor referenciában használni, ha rendelkezik egy olyan konstruktorral, ami String-et vár.
pl:
}
</source>
Vagyis azt mondja meg, hogy vagy a MyFunction implementálásával vagy egy inline lambda kifejezéssel gyártsunk olyan kódot, ami a saját osztály String változóját (varibale1) átalakítja egy MyString osztállyá, amit ő aztán fel tud használni a metódusban (jelen esetben kiíratjuk).
Még nem példányosodik a MyString osztály. Csak akkor mikor a funkcionális IF meghívásra kerül, de azt simán behelyettesíti egy olyan konstruktor hívással, ami ugyan azokat a paramétereket várja, és a '''::new'''-val megadott osztálynak semmi köze a funkcionális IF-hez (MyFunction), nem implementálja azt.
<br>
<br>
==Constructor reference Functional interface in Enum=ENUM constructors=
* Ehhez elsőként ajánlott elolvasni az ENUM alapokat: [[ Java Enum]]
* Második lépésnek pedig ezt: [[ Java_Lambda#Constructor_Reference ]]
==Alapok áttekintése==A [[ Java Enum]] cikkben láthattuk, hogy az enumoknak is lehet konstruktort, metódusokat és osztály változókat készíteni.Ismétlés képen itt egy enum, aminek van egy int változója, és ezt a konstruktor inicializálja, vagyis minden ENUM érték esetében meg kell adni egy 'int' számot is, amit el fog rakni az enum példányba: <source lang="java">public enum AdamTest {  LALI(2), ADAM(3) ;  private int enumInt;  private AdamTest(int a) { this.. TODO..enumInt=a; }}</source>
<br><br>Ha olyan osztály változót készítünk az enumENUM-nak készítünk Funkcionális , ami egy funkcionális interfész metódusokat , és ezt szintén a konstruktorral inicializáljuk, akkor minden egyes ENUM érték esetében a konstruktorban meg kell adni ennek a funkcionális interfésznek a definícióját.
Tegyük fel, hogy adott az alábbi osztály, ami egy saját String változat:
<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>
Tegyük fel, hogy adott az alábbi funkcionális IF:
<source lang="java">
@FunctionalInterface
public interface MyFunction {
MyString process(String input);
}
</source>
Itt a 'process(..)' implementáció egy string-et vár és egy MyString-et ad vissza.
 
<br>
Tekintsük az alábbi ENUM-ot. Az enum-nak van egy MyFunction típusú funkcionális if osztály változója, amit a konstruktor inicializál. Tehát minden enum értéknek meg kell adni egy MyFnction implementációt.
<source lang="java">
public enum MyEnum2 {
 
ENUMV1(..<MyFunction implementáció>...),
;
 
private final MyFunction function;
 
private MyEnum2(MyFunction function) {
 
System.out.println("ENUM constructor is called with input");
this.function = function;
}
 
public MyFunction getFunction() {
return function;
}
}
</source>
<br>
Láttuk már, hogy elvi szinten egy funkcionális if megadására 4 lehetőségünk van, azonban itt most nem használhatjuk mindent:
* Implementációt készítünk a MyFunction-hez: Ez nem fog működni, mert az enum példányosításakor statikus kontextusban vagyunk, nincs hol példányosítsuk a funkcionális if implementációnkat, nem olyan mint amikor enum kívül, normál class metódusban használjuk.
* lambda kifejezésben adjuk meg inline
* Metódus referenciát használunk egy olyan osztály metódusával, ami String-et vár és gyárt belőle egy MyString-et
* Konstruktor referenciát használunk
 
 
 
 
===lambda kifejezésben adjuk meg inline===
Az vetődhet fel kapásból bennünk, hogy az itt lambda-val definiált funkcionális if implementáció (vagyis a 'MyString process(String) impl') mikor fog lefutni, ki fogja meghívni, és mi lesz az a String érték amin dolgozni fog. A korábbi példákban mindig volt egy akármilyen metódus, ami várta paraméterül a funkcionális if-et, majd meghívta rajta a funkcionális metódusát (jelen esetben a process(Stringt)-et), úgy, hogy az a bizonyos metódus töltötte fel az összes input paraméterrel a funkcionális if hívást. Pszeudó kóddal ez elvi szinten így néz ki amiről itt beszélek:
<source lang="java">
class MyClassUsingFunctionIF {
 
void myMethodUsingFunctionIF(MyFunction mf) {
 
String inputForFuncion = "this is the input";
MyString result = mf.process(inputForFunction);
}
}
</source>
Itt a 'myMethodUsingFunctionIF(..)' meghívásával tudjuk implicit lefuttatni a funkcionális if implementációt. A process(..) input paraméterét saját belső változójából tölti fel.
 
 
ENUM konstruktor referencia estében el kell kérni az enum-tol a konstruktorban inicializált funkcionális függvényt, és meg kell hívni a funkcionális interfészét úgy hogy feltöltjük azt a megfelelő paraméterekkel. Nézzük ezt részletesen. Az alábbi példában az enum konstruktorában egy 'MyFunction' funkcionális interfészt kell átadni, amit egy inline lambda kifejezéssel definiáltunk (mármint a process metódusát).
<source lang="java">
public enum MyEnum2 {
 
ENUMV1(var1 -> {
System.out.println("ENUM lambda is called with input: " + var1);
return new MyString(var1);
}),
;
</source>
 
{note||Az itt megadott kód ugyebár nem fog lefutni a a MyEnum2 példányosításakor. A példányosításkor a konstruktorban ezzel a lambda kifejezéssel csak megadtuk a MyFunction if tervrajzát, vagyis hogy a MyFunction osztály változó rakjon bele egy olyan "virtuális" implementációs példányt, amiben az itt megadott 'MyString process(String)' implementáció található. }}
 
 
Ahhoz hogy a 'process(String)'-et implementáló lambda kifejezésünk lefusson, elsőként példányosítani kell az ENUM-ot a megfelelő típusra, arra, ami a lambda kifejezést tartalmazza, ami nem más mint az '''ENUMV1''' (lehetne több értéke is az enum-nak, és mindegyikben más és más implementációt is megadhattunk volna). Majd a példányosított enmum-tól a getter-el el kell kérni a funkcionális IF osztály változót, aminek meg kell hívni a process(..) metódusát, úgy hogy megadjuk az if-en kötelező egy darab String input paramétert.
 
<source lang="java">
System.out.println("START");
 
MyEnum2 myEnum21 = MyEnum2.ENUMV1;
System.out.println("Enum was created");
MyString result = myEnum21.getFunction().process("process input"); <<---- itt fut csak le a konstruktorban megadott lambda
</source>
Fontos, hogy az ENUM példányosításakor tudunk semmit "kívülről" megadni, tehát, nem lehet így példányosítani az ENUM-ot:
<pre>
MyEnum2.ENUMV1(..constructor values...); <------WRONG
</pre>
 
Nézzük meg futási sorrendet, ezt logolja az stdout-ra. Látható, hogy a lambda-ba megadott kifejezés csak a 'process(..)' metódus meghívásakor futott le. A lambda kifejezés tartalmazott egy 'MyString' példányosítást, az futott le utoljára.
<pre>
START
ENUM constructor is called with input
Enum was created
ENUM lambda is called with input: process input
MyString consturctor is called with input:process input
</pre>
<br>
<br>
 
===Metódus referenciát használunk===
Metódus referenciát használunk egy olyan osztály metódusával, ami String-et vár és gyárt belőle egy MyString-et:
 
 
<br>
<br>
 
===Konstruktor referenciát használunk===
Emlékezzünk rá, hogy funkcionális IF definíciókat meg kelhet adni azon osztály konstruktor referenciájával is, amikkel a funkcionális metódus visszatér, abban az esetben ha ez az osztály rendelkezik olyan konstruktorral, aminek a paraméterei megegyeznek a funkcionális metódus paramétereivel.
 
Pl. ha adott a már ismert Funkcionális if:
<source lang="java">
@FunctionalInterface
public interface MyFunction {
MyString process(String input);
}
</sring>
Akkor a funkcionális if definiálható a '''MyString::new''' konstruktor referenciával, ha rendelkezik String paraméterű konstruktorral:
<source lang="java">
public class MyString {
 
private String input;
 
public MyString(String input) { <<<<---------- ez itt a lényeg!!!!
System.out.println("MyString consturctor is called with input:" + input);
this.input = input;
}
 
public String getInput() {
return input;
}
}
</source>
 
<br>
Nézzük a már ismert java ENUM-ot, ezt egészítsük ki egy új értékkel: '''ENUMV2''', ahol a funkcionális if-et a konstruktorban a 'MyString' konstruktor referenciájával adjuk meg:
<source lang="java">
public enum MyEnum2 {
ENUMV1(var1 -> {
System.out.println("ENUM lambda is called with input: " + var1);
return new MyString(var1);
}),
ENUMV2(MyString::new) <<<-------------- ez itt a lényeg!!!
;
private final MyFunction function;
 
private MyEnum2(MyFunction function) {
 
System.out.println("ENUM constructor is called with input");
this.function = function;
}
 
public MyFunction getFunction() {
return function;
}
}
</source>
 
Ha példányosítjuk az enum-ot a ENUMV2 értékkel, majd azon meghívjuk a funkcionális if process(..) metódusát, akkor fog lefutni a MyString konstruktora:
<source lang="java">
System.out.println("START");
MyEnum2 myEnum22 = MyEnum2.ENUMV2;
System.out.println("Enum was created");
myEnum22.getFunction().process("prcoess again");
</source>
 
<br>
Az output az alábbi lesz:
<pre>
START
Enum was created
MyString consturctor is called with input:prcoess again
</pre>
<br>
<br>
 
==Mire használható (komplex példa)==
<br>
<br>