- Bevezetés
- További olvasmány:
- Iterable to Stream in Java
- hogyan kell használni az if/else logikát a Java 8 streamekben
- Lambdas Java 8
- funkcionális interfészek
- Functions
- primitív függvény specializációk
- Two-Arity Function Specializations
- szállítók
- fogyasztók
- predikátumok
- operátorok
- régebbi funkcionális interfészek
- következtetés
Bevezetés
Ez a bemutató egy útmutató a különböző funkcionális interfészek jelen Java 8, valamint az általános használat esetén, valamint a használat a standard JDK könyvtár.
További olvasmány:
Iterable to Stream in Java
hogyan kell használni az if/else logikát a Java 8 streamekben
Lambdas Java 8
Java 8 hozott egy erőteljes új szintaktikai javulás formájában lambda kifejezések. A lambda egy névtelen funkció, amelyet első osztályú nyelvi állampolgárként kezelhetünk. Például átadhatjuk vagy visszaadhatjuk egy módszerből.
A Java 8 előtt általában minden esetben létrehoznánk egy osztályt, ahol egyetlen funkcionalitást kellett beágyaznunk. Ez sok felesleges boilerplate kódot tartalmazott, hogy meghatározzon valamit, ami primitív függvény reprezentációként szolgált.
a “Lambda kifejezések és funkcionális interfészek: tippek és bevált gyakorlatok” című cikk részletesebben ismerteti a lambdákkal való együttműködés funkcionális interfészeit és bevált gyakorlatait. Ez az útmutató a java-ban jelen lévő egyes funkcionális interfészekre összpontosít.util.funkció csomag.
funkcionális interfészek
ajánlott, hogy minden funkcionális interfésznek legyen informatív @FunctionalInterface kommentárja. Ez egyértelműen közli az interfész célját, valamint lehetővé teszi a fordító számára, hogy hibát generáljon, ha a jegyzetelt felület nem felel meg a feltételeknek.
a Sam(single Abstract Method) bármely interfésze funkcionális felület, implementációja lambda kifejezésként kezelhető.
vegye figyelembe, hogy a Java 8 alapértelmezett módszerei nem elvontak és nem számítanak; a funkcionális interfésznek több alapértelmezett módszere is lehet. Ezt a funkció dokumentációjának megtekintésével megfigyelhetjük.
Functions
a lambda legegyszerűbb és legáltalánosabb esete egy funkcionális interfész egy metódussal, amely egy értéket kap, és egy másik értéket ad vissza. Ez a funkció egyetlen érv képviseli a Funkció felület, amely kódokat a típusú a vita, valamint egy visszatérési érték:
public interface Function<T, R> { … }
a használat, a Függvény írja be a standard könyvtár a Térkép.computeIfAbsent módszer. Ez a módszer egy értéket ad vissza egy térképből kulcs szerint, de kiszámítja az értéket, ha egy kulcs még nincs jelen a térképen. Kiszámításához értéket használ az elmúlt Funkció megvalósítása:
Map<String, Integer> nameMap = new HashMap<>();Integer value = nameMap.computeIfAbsent("John", s -> s.length());
ebben Az esetben, hogy kiszámítja az érték alkalmazásával egy olyan funkció, hogy egy kulcs, tedd be egy térképet, meg is tért vissza egy módszer hívás. Helyettesíthetjük a lambda-t egy olyan módszer-hivatkozással, amely megfelel az átadott és visszaadott értéktípusoknak.
ne feledje, hogy egy objektum, amelyre a módszert hívjuk, valójában egy módszer implicit első argumentuma. Ez lehetővé teszi számunkra, hogy leadott egy példány módszer hossza hivatkozás egy függvény interfész:
Integer value = nameMap.computeIfAbsent("John", String::length);
A Funkció felület is van egy alapértelmezett össze módszer, amely lehetővé teszi számunkra, hogy összekapcsolják a több funkció be egyet, aztán kivégezzük őket egymás után:
Function<Integer, String> intToString = Object::toString;Function<String, String> quote = s -> "'" + s + "'";Function<Integer, String> quoteIntToString = quote.compose(intToString);assertEquals("'5'", quoteIntToString.apply(5));
A quoteIntToString funkció kombinációja az idézet funkció alkalmazott eredménye a intToString funkció.
primitív függvény specializációk
mivel egy primitív típus nem lehet általános típusú argumentum, vannak változatai a függvény interfész a leggyakrabban használt primitív típusok dupla, int, hosszú, és ezek kombinációi argumentum és visszatérő típusok:
- IntFunction, LongFunction, DoubleFunction: arguments are of specified type, return type is parameterized
- ToIntFunction, ToLongFunction, ToDoubleFunction: return type is of specified type, arguments are parameterized
- DoubleToIntFunction, DoubleToLongFunction, IntToDoubleFunction, IntToLongFunction, LongToIntFunction, LongToDoubleFunction: miután mindkét argumentum visszatérési típus meghatározása a primitív típusok által megadott, a nevüket
például nincs out-of-the-box funkcionális interfész egy függvényt, ami tart egy rövid, de visszatér egy bájt, de semmit nem tudunk írni a saját:
@FunctionalInterfacepublic interface ShortToByteFunction { byte applyAsByte(short s);}
Most lehet írni egy módszer, ami átalakítja egy sor rövid ahhoz, hogy egy array of byte használata szabály által meghatározott a ShortToByteFunction:
public byte transformArray(short array, ShortToByteFunction function) { byte transformedArray = new byte; for (int i = 0; i < array.length; i++) { transformedArray = function.applyAsByte(array); } return transformedArray;}
Itt van, hogy hogyan lehetne használni, hogy átalakítsa egy sor rövidnadrág, hogy egy sor bájt szorozva 2:
short array = {(short) 1, (short) 2, (short) 3};byte transformedArray = transformArray(array, s -> (byte) (s * 2));byte expectedArray = {(byte) 2, (byte) 4, (byte) 6};assertArrayEquals(expectedArray, transformedArray);
Two-Arity Function Specializations
a lambdas két argumentummal történő meghatározásához további interfészeket kell használnunk, amelyek a nevükben” Bi ” kulcsszót tartalmaznak: BiFunction, ToDoubleBiFunction, ToIntBiFunction, and ToLongBiFunction.
A Bifunkciónak mind Az argumentumai, mind a visszatérési típusa általánosítható, míg a Todoublebifunkció és mások lehetővé teszik számunkra, hogy egy primitív értéket visszaadjunk.
Az interfész használatának egyik tipikus példája a Standard API-ban a térképen található.replaceAll módszer, amely lehetővé teszi, hogy cserélje ki az összes értéket a térképen néhány számított értéket.
használjunk egy bifunkciós implementációt, amely egy kulcsot és egy régi értéket kap a fizetés új értékének kiszámításához és visszaadásához.
Map<String, Integer> salaries = new HashMap<>();salaries.put("John", 40000);salaries.put("Freddy", 30000);salaries.put("Samuel", 50000);salaries.replaceAll((name, oldValue) -> name.equals("Freddy") ? oldValue : oldValue + 10000);
szállítók
A szállító funkcionális felülete egy újabb funkció specializáció, amely nem vesz fel semmilyen érvet. Általában az értékek lusta generálására használjuk. Például definiáljunk egy olyan funkciót,amely kettős értéket négyzetez. Nem maga kap értéket, hanem ennek az értéknek a szállítója:
public double squareLazy(Supplier<Double> lazyValue) { return Math.pow(lazyValue.get(), 2);}
Ez lehetővé teszi számunkra, hogy lazán generáljuk a függvény meghívására vonatkozó érvet egy beszállítói megvalósítás segítségével. Ez akkor lehet hasznos, ha az érvelés generálása jelentős időt vesz igénybe. Ezt szimuláljuk a Guava sleepUninterruptibly módszerével:
Supplier<Double> lazyValue = () -> { Uninterruptibles.sleepUninterruptibly(1000, TimeUnit.MILLISECONDS); return 9d;};Double valueSquared = squareLazy(lazyValue);
egy másik felhasználási eset a Szállító számára a szekvenciatermelés logikáját határozza meg. Ennek bemutatásához használjunk statikus áramot.módszer létrehozása a Fibonacci számok Streamjének létrehozásához:
int fibs = {0, 1};Stream<Integer> fibonacci = Stream.generate(() -> { int result = fibs; int fib3 = fibs + fibs; fibs = fibs; fibs = fib3; return result;});
az a funkció, amelyet átadunk az adatfolyamnak.generate módszer megvalósítja a szállító funkcionális interfész. Figyeljük meg, hogy hasznos lehet generátorként, A Szállítónak általában valamilyen külső állapotra van szüksége. Ebben az esetben az állapota az utolsó két Fibonacci szekvenciaszámot tartalmazza.
ennek az állapotnak a megvalósításához néhány változó helyett tömböt használunk, mivel a lambda-ban használt összes külső változónak hatékonyan véglegesnek kell lennie.
A szállító funkcionális interfészének egyéb szakterületei közé tartozik a BooleanSupplier, a DoubleSupplier, a LongSupplier és az IntSupplier, amelyek visszatérési típusai megfelelő primitívek.
fogyasztók
A szállítóval szemben a fogyasztó általános érvelést fogad el, és semmit sem ad vissza. Ez egy olyan funkció, amely a mellékhatásokat képviseli.
például köszöntsünk mindenkit a nevek listájában az üdvözlő nyomtatásával a konzolon. A lambda átkerült a listára.a forEach metódus a fogyasztói funkcionális interfészt valósítja meg:
List<String> names = Arrays.asList("John", "Freddy", "Samuel");names.forEach(name -> System.out.println("Hello, " + name));
a fogyasztó speciális változatai is vannak — DoubleConsumer, IntConsumer és LongConsumer—, amelyek primitív értékeket kapnak érvként. Érdekesebb a BiConsumer felület. Az egyik felhasználási eset egy térkép bejegyzésein keresztül iterálódik:
Map<String, Integer> ages = new HashMap<>();ages.put("John", 25);ages.put("Freddy", 24);ages.put("Samuel", 30);ages.forEach((name, age) -> System.out.println(name + " is " + age + " years old"));
a speciális BiConsumer verziók Egy másik csoportja ObjDoubleConsumer, ObjIntConsumer és ObjLongConsumer, amelyek két érvet kapnak; az egyik érv általánosításra kerül, a másik pedig primitív típus.
predikátumok
a matematikai logikában a predikátum olyan függvény, amely értéket kap, és logikai értéket ad vissza.
a predikátum funkcionális felület egy olyan függvény specializációja, amely általánosított értéket kap, és logikai értéket ad vissza. Egy tipikus felhasználás esetén az Állítmány lambda, hogy a szűrő egy gyűjtemény értékek:
List<String> names = Arrays.asList("Angela", "Aaron", "Bob", "Claire", "David");List<String> namesWithA = names.stream() .filter(name -> name.startsWith("A")) .collect(Collectors.toList());
a fenti kód, akkor a szűrő lista segítségével a Patak API csak a nevek, hogy az “A”betűt. A predikátum implementáció magába foglalja a szűrési logikát.
mint az összes korábbi példában, ennek a függvénynek vannak intrpredicate, DoublePredicate és LongPredicate változatai, amelyek primitív értékeket kapnak.a
operátorok
operátor interfészek egy olyan függvény speciális esetei, amely ugyanazt az értéktípust fogadja és adja vissza. Az UnaryOperator felület egyetlen argumentumot kap. A Collections API egyik felhasználási esete a listában szereplő összes érték helyettesítése néhány azonos típusú számított értékkel:
List<String> names = Arrays.asList("bob", "josh", "megan");names.replaceAll(name -> name.toUpperCase());
a lista.replaceAll függvény visszatér érvénytelen, mivel helyettesíti az értékeket a helyén. A cél eléréséhez a lista értékeinek átalakításához használt lambda-nak ugyanazt az eredménytípust kell visszaadnia, mint amennyit kap. Ezért hasznos az UnaryOperator itt.
természetesen név helyett – > név.toUpperCase (), egyszerűen csak egy módszer referencia:
names.replaceAll(String::toUpperCase);
a BinaryOperator egyik legérdekesebb felhasználási esete a redukciós művelet. Tegyük fel, hogy az egész számok gyűjteményét az összes érték összegével szeretnénk összesíteni. A Stream API használatával ezt egy gyűjtő segítségével tehetjük meg, de általánosabb módszer a reduce metódus használata:
List<Integer> values = Arrays.asList(3, 5, 8, 9, 12);int sum = values.stream() .reduce(0, (i1, i2) -> i1 + i2);
A reduce metódus kezdeti akkumulátorértéket és BinaryOperator függvényt kap. Ennek a függvénynek az érvei egy azonos típusú értékpár; maga a függvény tartalmaz egy logikát is, amely egyetlen azonos típusú értékhez csatlakozik. Az átadott függvénynek asszociatívnak kell lennie, ami azt jelenti, hogy az értékösszesítés sorrendje nem számít, azaz a következő feltételnek kell lennie:
op.apply(a, op.apply(b, c)) == op.apply(op.apply(a, b), c)
a BinaryOperator operátor operátor funkció asszociatív tulajdonsága lehetővé teszi számunkra, hogy könnyen párhuzamosítsuk a redukciós folyamatot.
természetesen vannak olyan unaryoperator és BinaryOperator szakterületek is, amelyek primitív értékekkel használhatók, nevezetesen Kettősunaryoperator, IntUnaryOperator, LongUnaryOperator, DoubleBinaryOperator, IntBinaryOperator és LongBinaryOperator.
régebbi funkcionális interfészek
nem minden funkcionális interfész jelent meg a Java 8-ban. A Java korábbi verzióinak számos interfésze megfelel egy FunctionalInterface korlátainak, ezeket lambdas-ként is használhatjuk. A kiemelkedő példák közé tartoznak a futtatható és hívható interfészek, amelyeket a párhuzamos API-kban használnak. A Java 8-ban ezeket az interfészeket @FunctionalInterface megjegyzéssel is megjelölik. Ez lehetővé teszi számunkra, hogy jelentősen egyszerűsítse konkurencia kód:
Thread thread = new Thread(() -> System.out.println("Hello From Another Thread"));thread.start();