Difference between revisions of "Java Funkcionális interfész & Lambda"

From berki WIKI
Jump to: navigation, search
(Constructor Reference)
(Constructor Reference)
Line 481: Line 481:
  
  
A konstruktor referenciát '''::new'''-val kel megadni, vagy a '''::'' operátor után a '''new''' kulcsszót kell írni. Egy lehetséges példa:
+
A konstruktor referenciát '''::new''' val kel megadni, vagy a '''::'' operátor után a '''new''' kulcsszót kell írni. Egy lehetséges példa:
  
 
Tegyük fel, hogy van egy java osztályunk, amiben van egy metódus, ami felhasználja a '''MyFunction''' funkcionális if-et:
 
Tegyük fel, hogy van egy java osztályunk, amiben van egy metódus, ami felhasználja a '''MyFunction''' funkcionális if-et:

Revision as of 10:08, 12 August 2022


<< Java


Lambda alapok

List-forEach()


List<String> items = new ArrayList<>();
	items.add("A");
	items.add("B");
	items.add("C");
	items.add("D");
	items.add("E");

	//lambda
	//Output : A,B,C,D,E
	items.forEach(item->System.out.println(item));


Ez miért működik?

A List a java.lang.Iterale osztály leszármazottja. Ebben van egy default metódus implementáció a forEach-re.

package java.lang;

public interface Iterable<T> {
...
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
...

Ez a Consumer<? super T> generikus interfész implementációt várja paraméterként, ahol a T a List-ben lévő lista elemek típusa. A T listaelemek bármilyen leszármazottját elfogadja. Láthatjuk az implementációból hogy a forEach metódus az összes listaelemen végigmegy, és minden egyes listaelemre meghívja a Consumer<T> implementáció accept() metódusát. A lista elemeket a saját osztály példányának a példány változójából szedi (vagyis ha van egy ArrayList típusú objektumunk, akkor az maga tárolja a lista elemeket egy belső változójában). A Consumer interfész implementáción múlik, hogy mit fog csinálni az adott listaelemmel.

A Consumer implementáció (Consumer<? super T> action) csak egy cső, amibe felülről be tudunk dobni egy T elemet és kidob alul egy R elemet (jelen esetben nincs R elem, mert void típusú a Funkcionális függvény). Tehát az implementációnak nincs állapota. Kap egy inputot és generál belőle egy outputot.



A Consumer interfészben az accept az egyetlen absztrakt metódus, tehát a Consumer egy funkcionális interfész, ahogy ez az annotációból is látszik:

package java.util.function;
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}
ImportantIcon.png

Note
A @FunctionalInterface annotáció hatására a fordító ellenőrzi, hogy az adott interfész valóban megfelel e a funkcionális interfészekkel támasztott követelményeknek.

  • Csak interfészre kerülhet ilyen annotáció
  • Az interfésznek pontosan 1 darab absztrakt metódusa van (vagyis implementálandó)



Nézzünk egy lehetséges implementációt:

public class MyConstumer implements Consumer<String>{
	@Override
	public void accept(String t) {
		System.out.println(t);		
	}
}

A MyConsumer osztály implementálja a Consumer interfész egyetlen metódusát, az accept-et, egy String-et vár inputba és nem ad vissza semmit.

Ennek az implementációs osztálynak ez lenne egy lehetséges felhasználása:

	public static void main(String[] args) {		
		List<String> items = new ArrayList<>();
		items.add("A");
		items.add("B");
		items.add("C");
		items.add("D");
		items.add("E");

		Consumer<String> myConsumer = new MyConstumer();
		items.forEach(myConsumer);		
	}



Azonban Java8-tól kezdve bevezették a λ kifejezéseket, mivel név nélküli inline függvény implementációkat készíthetünk, ezek tulajdonképpen input és output paraméterrel rendelkező kód blokkok az alábbi szintaxissal (alább három kül. példa)

parameter -> expression
(parameter1, parameter2) -> expression
(parameter1, parameter2) -> { code block }
WarningIcon.png

Warning
Ezek a paraméterek nem a "külső", funkcionális interfészt implementáló java osztályt meghívó metódus bemeneti paraméterei, hanem belső változok. A forEach esetén pl ezek a lista elemek



Az előző példa Lambda alakja a következő lenne:

                Consumer<String> myConsumer = item -> System.out.println(item);
		items.forEach(myConsumer);


És a lambda kifejezést írhatjuk kapásból a forEach argumentumába hogy rövidítsünk:

items.forEach(item->System.out.println(item));
ImportantIcon.png

Note
Az item kívülről nézve nem értelmezhető, szemben egy "normál" függvény hívással, ahol kintről adunk át neki paramétereket. Tehát ez NEM egy külső paraméter. Értelmet csak a forEach belsejében nyer. A lambad kifejezés csak egy állapot nélküli transzformációs függvény, ami inputnak megkapja az 'item nevű változót és csinál vele valamit:




A Lambda kifejezést kicserélhetjük method reference-re is:

items.forEach(System.out::print);

Ez egy statikus metódus referencia, amivel azt mondjuk meg, hogy a Consumer implementációja a System.out.print(T) metódust fogja meghívni. A lényeg, hogy egy argumentumos legyen a megadott metódus és fel tudjon dolgozni T lista típusú elemet.



Példa 2: saját implementáció

Az alábbi osztálynak van egy darab osztály változója (variable1) és van egy darab metódusa: processVariable. Ez a metódus egy funkcionális interfészt vár input paraméterként.

package hu.otp.auth.loginHelper.util;
import java.util.function.Function;

public class MyClassUsingFunctionalif {

	private String variable1;

	public MyClassUsingFunctionalif(String a) {
		this.variable1 = a;
	}

	public void processVariable(Function<String, Integer> fn) {

		Integer length = fn.apply(variable1);
		System.out.println(length.toString());
	}
}


A processVariable belsejében meghívja a funkcionális interfész apply() metódusát. (Definiálhattunk volna magunknak saját funkcionális interfész típust, mi most itt egy gyári típust használunk).

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Function<T, R> {
    R apply(T var1);   <<<<<<<<<<<-------------------------------------------------ez itt a lényeg!!!

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (v) -> {
            return this.apply(before.apply(v));
        };
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (t) -> {
            return after.apply(this.apply(t));
        };
    }

    static <T> Function<T, T> identity() {
        return (t) -> {
            return t;
        };
    }
}

Láthatjuk, hogy egy implementálandó metódusa van a "Function" interfésznek, ami T típust vár és R típus ad vissza. Tehát ez egy generikus interfész, és bármi is legyen az implementáció, ott majd meg kell mondani, hogy a pl T=String és R=Intger. De az hogy a Function az egy generikus interfész (generic) annak semmi köze ahhoz hogy ő egy Funkcionális interfész.

Hagyományos implementáció

A Function interfésznek elkészíthetjük a saját implementációnkat is:

    class MyFunctionImpl implements Function<String, Integer> {
        @Override
        public Integer apply(String s) {
            return Integer.valueOf(s.length());
        }
    }

Az előbb láthattunk, hogy egy metódust kell kötelezően definiálni a Function interfészen, ez az apply. Az implementáció első sorában kikötöttük, hogy T és R String és Integer lesz. Tehát az apply() implementációnknak S-t kell kapnia paraméterként és Integer-t kell visszaadnia. Ez itt teljesül. Nem csinál mást mint a kapott String hosszát visszaadja.

Ahhoz hogy ezt használni tudjuk, példányosítani kell, és át kell adni processVariable() metódusnak, ami egy példányosított funkcionális interfészt implementáló osztályt vár. Vagyis egy olyan objektumot, ami egy olyan osztályból készült, ami implementálja a funkcionális interfész metódusát.


        MyClassUsingFunctionalif MyClassUsingFunctionalif = new MyClassUsingFunctionalif("adam");

        MyFunctionImpl myFunction = new MyFunctionImpl();

        MyClassUsingFunctionalif.processVariable(myFunction);


A processVariable() implementációját mi készítettük, tudjuk hogy nem csinál mást, mint meghívja a myFunction.apply() metódust.

λ implementáció

Nézzünk az alábbi lehetséges λ implementációt (inline). Az apply-nak átadja a saját osztály változóját, majd az apply által visszaadott Integer-t kiírja a sysout-ra. Az hogy az apply belsejében hogy áll elő az Ingerer és hogy mit csinál a bemenő String paraméterrel teljes egészében az apply implementációra van bízva.
Nézzünk meg egy lehetséges implementációt:

	public static void main(String[] args) {

		MyClassUsingFunctionalif MyClassUsingFunctionalif = new MyClassUsingFunctionalif("adam");		
		MyClassUsingFunctionalif.processVariable(var1 -> { return Integer.valueOf(var1.length()); });

	}

Ebből az apply lambad kifejezéssel történő inline implementációja az alábbi:

var1 -> { return Integer.valueOf(var1.length()); }

Ami nem csinál mást, mint visszaadja a kapott string hosszát. Kívülről nézve a var1 nem értelmezhető, az mindig a lamdát futtató osztály egy osztály változója, kívülről nem megadható.

WarningIcon.png

Warning
Itt a 'var1' nem kívülről jövő, a metódus meghívásakor előállt paraméter! Ez a 'MyClassUsingFunctionalif' belső változója, vagyis ide kívülről nem tudunk változót betolni a metódus meghívásakor


Tehát, ahogy ezt majd látni fogjuk a CompletionSage-nél, a 'FunctionalInterface'-t futtató osztályt kell előre feltölteni minden olyan változóval, amire szükség van a lamda kifejezés futtatására. A fenti példákban ez egyrészt a 'MyClassUsingFunctionalif', vagy az első példában a ArrayList osztályok.

  • A 'MyClassUsingFunctionalif' konstruktorában adtuk át azt a string-et, amit a 'processVariable' feldolgoz, attól függetlenül, hogy milyen lamda kifejezéssel implementáljuk a funkcionális interfészét.
  • Az ArrayList pedig belső változóiban tárolja



Példa 3: CompletionStage.thenCompose()

Mi is ez?

  • CompletionStage Interface:
    • ezek egymás után futtatott stage-ek, ezért hívják "Comletion"-nek. Mikor az egyik stage véget ér, potenciálisan elindít egy másik "CompletionStage"-et és így tovább, egymásba láncolva. Minden metódusa egy másik CompletionStage-et ad vissza. De ez csak egy interfész, vmilyen implementációját kell használni.
    • java.util.concurrent.CompletionStage<T> interface represents a commutation task (either synchronous or asynchronous). As all methods declared in this interface return an instance of CompletionStage itself, multiple CompletionStages can be chained together in different ways to complete a group of tasks.
  • CompletableFuture<T> implements CompletionStage<T> and Future<T>: The static factory methods in this class are the starting points for executing tasks.


Nézzünk egy példát a CompletionStage lánchívására. A CompletionStage minden metódusa CompletionStage-el tér vissza, köztük a thenComponse() is, ezért hívható láncban.

method1().thenCompose(var1 -> method2(var1))
         .thenCompose(this::method3))
         .thenCompose(var2 -> { System.out.println(var2.printMe()); 
                               System.out.println("That was it"); return new CompletionStage(..) ; 
                              } )
         .whenComplete((result, error) -> { ...}

A láncot indító metódusnak értelem szerűen egy CompletionStage példánnyal kell visszatérnie.

A thenCompose szignatúrája az alábbi:

<U> LogCompletionStage<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> var1);



Láthatjuk, hogy egy Function interfészt vár paraméterül. A 'Function' interfésze nem más mint egy "gyári" funkcionális interfész típus (olyan mint az előző példába a 'Consumer' interfész volt). A funkcionális interfészekkel szemben támasztott követelmény, hogy egy nem implementált metódusuk legyen, ami ebben az esetben az apply(). A T az input típusa, míg R a visszatérési típus.

@FunctionalInterface
public interface Function<T, R> {
    R apply(T var1);
    ...
}




Tehát a thenCompose(..) vár egy Function interfész implementációt és belül meg fogja hívni az apply(..) metódusát, aminek át fog adni egy T típusú változót, és R típusú változót fog visszavárni visszatérési értékként. Nézzük meg a thenCompose implementációját:

    public <U> CompletableFuture<U> thenCompose(
        Function<? super T, ? extends CompletionStage<U>> fn) {
        return uniComposeStage(null, fn);
    }

    private <V> CompletableFuture<V> uniComposeStage(
        Executor e, Function<? super T, ? extends CompletionStage<V>> f) {
        if (f == null) throw new NullPointerException();
        CompletableFuture<V> d = newIncompleteFuture();
        Object r, s; Throwable x;
        if ((r = result) == null)
            unipush(new UniCompose<T,V>(e, d, this, f));
        else if (e == null) {
            if (r instanceof AltResult) {
                if ((x = ((AltResult)r).ex) != null) {
                    d.result = encodeThrowable(x, r);
                    return d;
                }
                r = null;
            }
            try {
                @SuppressWarnings("unchecked") T t = (T) r;
                CompletableFuture<V> g = f.apply(t).toCompletableFuture();
        ....
    }

Anélkül hogy nagyon belemennénk a részletekbe, a lényeg itt is az mint a 2. példában. A CompletableFuture példánynak vannak példány változói. Fogja a T típusú változóját és meghívja vele az apply(..) metódust, és visszavár egy CompletionStage leszármazottat. Mivel a CompletionStage hívások láncba fűzhetőek, a hívások egymás után hajtódnak végre. A fenti kódrészlet egy kis varázslással végrehajtja és előveszi a láncban az előző CompletionStage<R> értékét és ebből az R-t adja oda az apply(...) metódusnak, hogy végezze el rajta a szükséges műveleteket.

Az apply(..) metódus inline implementációja tehát mindig egy CompletionStage<R> példánnyal kell hogy visszatérjen, ahonnan az R-t fogja megkapni a következő thenCompose hívás inline implementációja T-ként. Pszeudokóddal leírva ez így néz ki:

   ... 
   .thenCompose(var1 -> {  ... 
                      //Create a future with string inside
                      new CompletableFuture<String> future = new CompletableFuture<String>(); 
                      future.complete("adam");
                      return future;
                })
   .thenCompose(var1 -> {
                       System.out.println(var1); //will print "adam"
                      ...
                      })
   ...

Az első thenCompose(..) vissza fog térni egy CompletableFuture-el, amiben egy String-et tárol el. A második thenCompose(..) hívás végre fogja hajtani az elsőt, majd annak az eredményéből ki fogja nyerni az "adam" Stringet és azzal fogja meghívni a Funkcionális interfész inline implementációt vagyis olyan mint ha ezt csinálná:

fn.apply("adam");


ImportantIcon.png

Note
Tehát az apply(..) inline implementációja csak egy cső, amibe a thenCompose() bedob egy változót és visszavár egy végeredményt. A változó itt is a saját példány változója (történetesen az előző futás eredménye)




Rövidítés

Na de mit jelent ez a sor:

         .thenCompose(this::method3)

Ez csak egy rövidítés. Egy statikus metódus referencia. Ahelyett hogy megadnánk egy inline implementációt, azt mondjuk meg, hogy ez a függvény végzi el azt a feladatot, amit az inline implementációnk végzett volna el.

<Melyik osztály>::<melyik metódusa>

Ezt akkor lehet alkalmazni, ha amúgy is csak csak metódus hívást írtunk volna a body-ba:

         .thenCompose(var2 -> method3(var2))



Java Method Reference and Constructor Reference

https://www.amitph.com/java-method-and-constructor-reference/

Method Reference

A metódus referencia (akár statikus akár nem) arra jó, hogy egy meglévő osztály egy metódusát át tudjuk adni a funkcionális IF definíciót váró metódusnak ahelyett hogy definiálnánk helyben lambda belsejét. Azonban ez nem egyezik meg a Funkcionális interfész implementációját tartalmazó osztály használatával. Ha metódus referenciát használunk, akkor a funkcionális IF implementáció helyére megadott osztály és metódus nem kell hogy implementálja a funkcionális interfész funkcionális metódusát, elég ha megfelel a karakterisztikája az éppen használt funkcionális if kifejezésnek, vagyis az elvárt paraméterekkel rendelkezik, és a visszatérési érték típusa is megfelel a várt funkcionális if-nek kifejezésnek.

Nézzük ismét az alábbi példa class-t, aminek van egy metódusa, ami egy funkcionális interfészt vár (processVariable). Itt egy beépített funkcionális IF-et használunk megint

package hu.otp.auth.loginHelper.util;
import java.util.function.Function;

public class MyClassUsingFunctionalif {

	private String variable1;

	public MyClassUsingFunctionalif(String a) {
		this.variable1 = a;
	}

	public void processVariable(Function<String, Integer> fn) {

		Integer length = fn.apply(variable1);
		System.out.println("Value given by function IF impl: "+ length.toString());
	}
}


Emlékeztetőül, a java gyári Function nevű funkcionális IF implementációja így néz ki:

package java.util.function;

@FunctionalInterface
public interface Function<T, R> {
    R apply(T var1);
...

Láthatjuk, hogy egy implementálandó metódusa van, ezt lehet egy lambda in line IF-el megadni, vagy akár egy implementációs osztállyal definiálni.

Tehát, normál esetben ez működne:

public class MyFunctionImpl implements Function<String, Integer> {
    @Override
    public Integer apply(String s) {
        return s.length();
    }
}

</br> Majd ha meghajtjuk a metódust ezzel az implementációval:

        MyClassUsingFunctionalif MyClassUsingFunctionalif = new MyClassUsingFunctionalif("process_this_during_functional_if_call");
        MyClassUsingFunctionalif.processVariable(new MyFunctionImpl());

Akkor a konzolon meg fog jelenni hogy: "38"


Mert hogy:
A processVariable() metódus belsejében azt mondtuk meg, hogy fogja meg a konstruktorban kapott változót, adja azt át a Function funkcionális if-et implementáló osztály apply metódusának és amit az visszaad, azt írja ki a képernyőre. A MyFunctionImpl -ban rögzítettük, hogy a generikus IF milyen típusokkal fog rendelkezni (String megy be, és Integer jön ki). Azt hogy ezt hogy állítja elő az implementáció, erről a processVariable() metódusban semmit sem tudunk, teljesen az implementációra van bízva.



Persze, ezt lambdával is megadhattuk volna, akkor így nézne ki:

        MyClassUsingFunctionalif MyClassUsingFunctionalif = new MyClassUsingFunctionalif("process_this_during_functional_if_call");
        MyClassUsingFunctionalif.processVariable(input -> {return input.length();});  ---> 38

Itt inline, lambda definíciót használtunk. A generikus változói a Function funkcionális interfésznek (T és R) explicit lett definiálva azáltal, hogy a processVariable(..) belsejében a funkcionális IF (apply) String paraméterrel van meghívva és Integer-t vár vissza.



Na már most, ezt lehet nagyban egyszerűsíteni bármilyen olyan Osztály statikus vagy nem statikus metódusával, ami String-et vár és Integer-t ad vissza. Erre szolgála :: operátor. Statikus metódus használata esetén nem kell példányosítani az osztályt, ennyi a különbség. Ami a lényeg: ezen osztálynak nem kell implementálnia a várt funkcionális interfész itt használt metódusát!!

        MyClassUsingFunctionalif MyClassUsingFunctionalif = new MyClassUsingFunctionalif("process_this_during_functional_if_call");
        MyClassUsingFunctionalif.processVariable(String::length);  ----> 38

Ezzel azt mondjuk meg, hogy az R pply(T) helyett inkább hívd meg az Integer Sring.length(String input) metódust.


VAGY ha nem statikus:

Tegyük fel hogy van egy ilyen osztályom, ami nem implementál semmilyen IF-et:

public class MyString {
    
    public Integer getLengthOfImput(String input) {
        return  input.length();
    }
}

Akkor a getLengthOfImput(..)' is átadható példányosítás után a :: operátorral, mint behelyettesítés:

        MyClassUsingFunctionalif MyClassUsingFunctionalif = new MyClassUsingFunctionalif("process_this_during_functional_if_call");
        
        MyString myString = new MyString();        
        MyClassUsingFunctionalif.processVariable(myString::getLengthOfImput); ---> 38

Vagyis ahelyett hogy meghívná a funkcionális IF R apply(T) metódusát, inkább meghívja majd a getLengthOfImput metódust.

Constructor Reference

A konstruktor referencia egy speciális Metódus referencia, ami csavar még egyet a fenti metódus referencia koncepción, itt már nagyon kell figyelni.
Ugyebár minden funkcionális IF visszaad egy objektumot, és vagy kap vagy nem input paramétereket. A lényeg, hogy egy előre nem definiált módon gyártson le egy olyan objektumot, amit visszaad akkor mikor meghívjuk a funkcionális metódusát, az előbbi példában az R apply(T) metódust. Ahol kap egy T-t és tök mindegy hogy hogy, csak gyártson le egy R osztály példányt.


Tekintsük az alábbi funkcionális IF-et:

@FunctionalInterface
public interface MyFunction {

    MyString process(String input);
}

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.
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 elő, hogy szimplán példányosítsd 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:

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;
    }    
}


A konstruktor referenciát ::new' val kel megadni, vagy a :: operátor után a new kulcsszót kell írni. Egy lehetséges példa:

Tegyük fel, hogy van egy java osztályunk, amiben van egy metódus, ami felhasználja a MyFunction funkcionális if-et:

public class MyClassUsingFunctionalif {

    private String variable1;

    public MyClassUsingFunctionalif(String a) {
        System.out.println("MyClassUsingFunctionalif consturctor is called with input:" + a);
        this.variable1 = a;
    }

    public void processVariable2(MyFunction mf) {

        System.out.println("MyClassUsingFunctionalif.processVariable2 START");
        MyString myString = mf.process(variable1);

        System.out.println("MyString instance created by function if call: "+ myString.getInput());

        System.out.println("MyClassUsingFunctionalif.processVariable2 END");
    }
}

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


És ahelyett hogy:

  • Implementációt készítünk a MyFunction-hez
  • 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

Simán megfogjuk a MyString osztályt és a konstruktorára hivatkozunk a MyClassUsingFunctionalif.processVariable2(..) metódus használatakor.

        MyClassUsingFunctionalif myClassUsingFunctionalif = new MyClassUsingFunctionalif("process_this_during_functional_if_call");
        myClassUsingFunctionalif.processVariable2(MyString::new);

És ez kerül az output-ra:

MyClassUsingFunctionalif consturctor is called with input:process_this_during_functional_if_call
MyClassUsingFunctionalif.processVariable2 START
MyString consturctor is called with input:process_this_during_functional_if_call
MyString instance created by function if call: process_this_during_functional_if_call
MyClassUsingFunctionalif.processVariable2 END


Tehát az egész MyString myString = mf.process(variable1); hívást helyettesítette a MyString konstruktor meghívásával. Láthatjuk a log sorrendből is, hogy ebben a sorban:

myClassUsingFunctionalif.processVariable2(MyString::new);

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.



Constructor reference in Enum

Ehhez elsőként ajánlott elolvasni az ENUM alapokat: Java Enum


Ha az enum-nak készítünk Funkcionális interfész metódusokat




Stream

https://stackify.com/streams-guide-java-8/ ..TODO..