Funkcionális interfészek a Java 8-ban

1. Bemutatkozás

Ez a cikk útmutató a Java 8-ban található különböző funkcionális interfészekről, azok általános használati eseteiről és a szokásos JDK könyvtárban történő használatáról.

2. Lambdas a Java 8-ban

A Java 8 erőteljes új szintaktikai javulást hozott a lambda kifejezések formájában. A lambda egy névtelen függvény, amelyet első osztályú nyelvi állampolgárként kezelhetünk, például átadhatunk egy metódusnak vagy visszaadhatunk róla.

A Java 8 előtt általában létrehoz egy osztályt minden olyan esetre, ahol egyetlen funkciót kell összefoglalnia. Ez sok felesleges kazán kódot tartalmazott annak meghatározásához, amely primitív függvényábrázolásként szolgált.

A lambdasokat, a funkcionális interfészeket és a velük való munkavégzés bevált módszereit általában a „Lambda kifejezések és funkcionális interfészek: tippek és legjobb gyakorlatok” című cikk ismerteti. Ez az útmutató néhány olyan funkcionális interfészre összpontosít, amelyek a java.util.function csomag.

3. Funkcionális interfészek

Minden funkcionális interfész számára ajánlott, hogy legyen informatív @FunctionalInterface annotáció. Ez nemcsak egyértelműen közli ennek az interfésznek a célját, hanem lehetővé teszi a fordító számára, hogy hibát generáljon, ha a feljegyzett felület nem felel meg a feltételeknek.

A SAM-tal (Single Abstract Method) rendelkező bármely interfész funkcionális interfész, és megvalósítása lambda kifejezésekként kezelhető.

Ne feledje, hogy a Java 8-asok alapértelmezett módszerek nem absztrakt és nem számítanak: egy funkcionális interfésznek mégis lehet több alapértelmezett mód. Ezt megfigyelheti a Funkció dokumentáció.

4. Funkciók

A lambda legegyszerűbb és legáltalánosabb esete egy funkcionális interfész olyan módszerrel, amely az egyik értéket megkapja, a másikat pedig visszaadja. Egyetlen argumentumnak ezt a funkcióját a Funkció interfész, amelyet argumentuma típusai és visszatérési értéke paraméterez:

nyilvános felület Funkció {…}

Az egyik felhasználása a Funkció típus a standard könyvtárban az Map.computeIfAbsent metódus, amely kulcs alapján adja vissza az értéket a térképről, de kiszámít egy értéket, ha egy kulcs még nem szerepel a térképen. Az érték kiszámításához az átadott Funkció megvalósítást használja:

Map nameMap = új HashMap (); Egész szám = névMap.computeIfAbsent ("John", s -> s.length ());

Ebben az esetben egy értéket úgy kell kiszámítani, hogy egy funkciót alkalmazunk egy kulcsra, beletesszük a térképbe, és egy metódushívásból visszaküldjük. Mellesleg, lecserélhetjük a lambda-t egy metódus referenciára, amely megfelel az átadott és a visszaadott értéktípusoknak.

Ne feledje, hogy egy objektum, amelyre a metódust hívják, valójában a metódus implicit első argumentuma, amely lehetővé teszi egy példány metódus öntését hossz hivatkozás a Funkció felület:

Egész érték = névMap.computeIfAbsent ("John", String :: hossz);

A Funkció interfész alapértelmezett összeállít módszer, amely lehetővé teszi több függvény egyesítését és egymás utáni végrehajtását:

Funkció intToString = Object :: toString; Funkció idézet = s -> "'" + s + "'"; Függvény quoteIntToString = quote.compose (intToString); assertEquals ("'5" ", quoteIntToString.apply (5));

A quoteIntToString függvény a idézet függvény, amelyet a intToString funkció.

5. Primitív funkcióspecializációk

Mivel egy primitív típus nem lehet általános típusú argumentum, a Funkció interfész a legtöbbet használt primitív típusokhoz kettős, int, hosszúés ezek kombinációi argumentum- és visszatérési típusokban:

  • IntFunction, LongFunction, DoubleFunction: Az argumentumok meghatározott típusúak, a visszatérés típusa paraméterezett
  • ToIntFunction, ToLongFunction, ToDoubleFunction: a return típus meghatározott típusú, az argumentumok paraméterezve vannak
  • DoubleToIntFunction, DoubleToLongFunction, IntToDoubleFunction, IntToLongFunction, LongToIntFunction, LongToDoubleFunction - az argumentum és a return típus is primitív típusként definiálva, a nevük szerint

Nincs egy dobozon kívüli funkcionális interfész mondjuk egy olyan funkcióhoz, amely a rövid és visszaadja a byte, de semmi sem akadályozza meg a saját írásában:

@FunctionalInterface nyilvános felület ShortToByteFunction {bájt ApplyAsByte (rövid s); }

Most írhatunk egy metódust, amely átalakítja a tömböt rövid tömbjére byte a által definiált szabály használatával ShortToByteFunction:

public byte [] transformArray (rövid [] tömb, ShortToByteFunction függvény) {byte [] transformedArray = új byte [tömb.hossz]; for (int i = 0; i <tömb.hossz; i ++) {transformedArray [i] = function.applyAsByte (tömb [i]); } return transformedArray; }

A következőképpen használhatjuk fel egy rövidnadrág tömb átalakítására bájt tömbre, szorozva 2-vel:

rövid [] tömb = {(rövid) 1, (rövid) 2, (rövid) 3}; byte [] transformedArray = transformArray (tömb, s -> (byte) (s * 2)); bájt [] várt array = {(byte) 2, (byte) 4, (byte) 6}; assertArrayEquals (várhatóArray, transformedArray);

6. Kétarifüggvényes specializációk

A lambdas definiálásához két argumentummal további interfészeket kell használnunk, amelyekKettős" kulcsszó a nevükben: BiFunction, ToDoubleBiFunction, ToIntBiFunction, és ToLongBiFunction.

BiFunction argumentumokat és visszatérési típust generál, míg a ToDoubleBiFunction mások pedig lehetővé teszik egy primitív érték visszaadását.

Az egyik tipikus példa ennek az interfésznek a standard API-ban való használatára az Map.replaceAll módszer, amely lehetővé teszi a térkép összes értékének helyettesítését valamilyen számított értékkel.

Használjuk a BiFunction végrehajtás, amely kulcsot és régi értéket kap, hogy kiszámítsa a fizetés új értékét és visszaadja azt.

Térkép fizetések = new HashMap (); fizetések.put ("John", 40000); fizetések.put ("Freddy", 30000); fizetések.put ("Sámuel", 50000); fizetések.replaceAll ((név, oldValue) -> név.egyenlő ("Freddy")? oldValue: oldValue + 10000);

7. Beszállítók

A Támogató funkcionális interfész még egy másik Funkció szakterület, amely nem hoz érveket. Jellemzően az értékek lusta előállításához használják. Például definiáljunk egy olyan függvényt, amely négyzetbe állítja a kettős érték. Nem maga fog értéket kapni, hanem a Támogató ennek az értéknek:

public double squareLazy (Beszállító lazyValue) {return Math.pow (lazyValue.get (), 2); }

Ez lehetővé teszi számunkra, hogy lustán generáljuk az argumentumot ennek a függvénynek a meghívására az a használatával Támogató végrehajtás. Ez akkor lehet hasznos, ha ennek az érvnek a létrehozása jelentős időt vesz igénybe. Ezt a Guava-val fogjuk szimulálni aludj megszakíthatatlanul módszer:

Szállító lazyValue = () -> {Uninterruptibles.sleepUninterruptibly (1000, TimeUnit.MILLISECONDS); visszatér 9d; }; Dupla valueSquared = négyzetLazy (lazyValue);

A Szállító másik felhasználási esete a szekvenciagenerálás logikájának meghatározása. Ennek bemutatásához használjunk statikát Stream.generate módszer a Folyam a Fibonacci-számok száma:

int [] fibs = {0, 1}; Fibonacci-patak = Stream.generate (() -> {int eredmény = fibs [1]; int fib3 = fibs [0] + fibs [1]; fibs [0] = fibs [1]; fibs [1] = fib3; visszatérési eredmény;});

Az a funkció, amelyet a Stream.generate módszer valósítja meg a Támogató funkcionális interfész. Figyelje meg, hogy a generátor hasznos, a Támogató általában valamilyen külső állapotra van szüksége. Ebben az esetben állapota két utolsó Fibonacci sorozatszámból áll.

Ennek az állapotnak a megvalósításához egy tömböt használunk pár változó helyett, mert a lambda belsejében használt összes külső változónak ténylegesen véglegesnek kell lennie.

Egyéb szakirányok Támogató funkcionális interfész tartalmazza BooleanSupplier, DoubleSupplier, LongSupplier és IntSupplier, amelynek visszatérési típusai megfelelő primitívek.

8. Fogyasztók

Szemben a Támogató, a Fogyasztó elfogad egy generált argumentumot, és nem ad vissza semmit. Ez egy olyan funkció, amely a mellékhatásokat képviseli.

Például köszöntsünk mindenkit a névlistában úgy, hogy kinyomtatjuk az üdvözletet a konzolon. A lambda átment a List.forMinden módszer valósítja meg a Fogyasztó funkcionális interfész:

Listanevek = Arrays.asList ("John", "Freddy", "Samuel"); names.forEach (név -> System.out.println ("Hello," + név));

Vannak speciális verziói is a FogyasztóDoubleConsumer, IntFogyasztó és LongConsumer - amelyek primitív értékeket kapnak érvként. Érdekesebb az BiConsumer felület. Az egyik felhasználási esete a térkép bejegyzésein keresztüli iteráció:

Térkép korai = new HashMap (); age.put ("János", 25); age.put ("Freddy", 24); age.put ("Sámuel", 30); age.forEach ((név, életkor) -> System.out.println (név + "" + kor + "éves"));

Egy másik speciális csoport BiConsumer verziók áll ObjDoubleConsumer, ObjIntConsumer, és ObjLongConsumer amelyek két argumentumot kapnak, amelyek közül az egyik generálódik, a másik pedig egy primitív típus.

9. predikátumok

A matematikai logikában az állítmány olyan függvény, amely értéket kap és logikai értéket ad vissza.

A Állítmány a funkcionális interfész a Funkció amely generált értéket kap és logikai értéket ad vissza. A Állítmány A lambda az értékek gyűjteményének szűrése:

Listanevek = Arrays.asList ("Angela", "Aaron", "Bob", "Claire", "David"); List namesWithA = nevek.stream () .filter (név -> név.startsWith ("A")) .collect (Collectors.toList ());

A fenti kódban egy listát szűrünk a Folyam API, és csak az „A” betűvel kezdődő neveket őrizze meg. A szűrési logika a Állítmány végrehajtás.

Mint minden korábbi példában, vannak ilyenek is IntPredicate, DoublePredicate és LongPredicate a függvény primitív értékeket kapó verziói.

10. Üzemeltetők

Operátor az interfészek a függvény speciális esetei, amelyek ugyanazt az értéktípust fogadják és adják vissza. A UnaryOperator interfész egyetlen argumentumot kap. Az egyik használati esete a Collections API-ban az, hogy a lista összes értékét lecseréli néhány azonos típusú számított értékre:

Listanevek = Arrays.asList ("bob", "josh", "megan"); names.replaceAll (név -> name.toUpperCase ());

A List.replaceAll függvény visszatér üres, mivel a helyén levő értékeket helyettesíti. A célnak való megfelelés érdekében a lista értékeinek átalakításához használt lambdának ugyanazt az eredménytípust kell visszaadnia, mint amit kapott. Ezért a UnaryOperator itt hasznos.

Természetesen ahelyett név -> név.UPperCase (), egyszerűen használhatja a módszer referenciáját:

names.replaceAll (String :: toUpperCase);

Az egyik legérdekesebb használati eset a BinaryOperator redukciós művelet. Tegyük fel, hogy egész számok gyűjteményét szeretnénk összesíteni az összes érték összegében. Val vel Folyam API, ezt megtehetnénk egy gyűjtővel, de ennek általánosabb módja a csökkenteni módszer:

List értékek = tömbök. AsList (3, 5, 8, 9, 12); int összeg = értékek.folyam () .csökkent (0, (i1, i2) -> i1 + i2); 

A csökkenteni módszer megkapja a kezdeti akkumulátor értékét és a BinaryOperator funkció. Ennek a függvénynek az argumentumai azonos típusú értékpárok, és maga a függvény tartalmaz egy logikát, amely összeköti őket egyetlen azonos típusú értékkel. Az átadott függvénynek asszociatívnak kell lennie, ami azt jelenti, hogy az értékek összesítésének sorrendje nem számít, vagyis a következő feltételnek kell teljesülnie:

op.alkalmaz (a, op.alkalmazz (b, c)) == op.alkalmazz (op.alkalmazz (a, b), c)

Az a asszociatív tulajdonsága BinaryOperator az operátor funkció lehetővé teszi a redukciós folyamat egyszerű párhuzamosítását.

Természetesen vannak szakirányai is UnaryOperator és BinaryOperator amelyet primitív értékekkel lehet használni, mégpedig DoubleUnaryOperator, IntUnaryOperator, LongUnaryOperator, DoubleBinaryOperator, IntBinaryOperator, és LongBinaryOperator.

11. Örökölt funkcionális interfészek

Nem minden funkcionális felület jelent meg a Java 8-ban. A Java korábbi verzióinak számos felülete megfelel az a korlátozásainak Funkcionális interfész és lambdaként használható. Kiemelkedő példa a Futható és Hívható interfészek, amelyeket a párhuzamossági API-kban használnak. A Java 8-ban ezeket az interfészeket szintén a jelöli @FunctionalInterface annotáció. Ez jelentősen leegyszerűsíti a párhuzamossági kódot:

Szálszál = új Szál (() -> System.out.println ("Hello egy másik szálból")); szál.indítás ();

12. Következtetés

Ebben a cikkben a Java 8 API-ban található különböző funkcionális interfészeket írtuk le, amelyek lambda-kifejezésekként használhatók. A cikk forráskódja elérhető a GitHubon.