Trendy v návrhu SW architektury
Pecinovský
[email protected]
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
1 z 71
Obsah 1. Terminologie 2. Funkční interfejsy a lambda výrazy 3. Nová koncepce konstrukce interface 4. Datový typ java.util.Optional
5. Datové proudy 6. KONEC
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
2 z 71
1. Terminologie Obsah 1.1Rozhraní × Interfejs 1.2Přetížení × zakrytí × přebití × předefinování
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
3 z 71
1.1 Rozhraní × Interfejs ►
Rozhraní Vlastnost entity programu určující, co okolní program o dané entitě ví a jak s ní může komunikovat
►
Rozhraní mí dvě složky: ● Signatura označuje tu jeho část, kterou může zkontrolovat překladač ● Kontrakt označuje část, o jejíž dodržení se musí postarat programátor
►
Interfejs Programová konstrukce umožňující definovat rozhraní bez implementace ● Počeštěný (a jazykovými slovníky povolený) tvar používám proto,
abych mohl termín v textu skloňovat
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
4 z 71
1.2 Přetížení × zakrytí × přebití × předefinování ►
Přetížení metody – method overloading Několik metod má stejný název a liší se pouze v počtu a/nebo typu svých parametrů ● Nestačí, pokud se liší pouze v názvu parametrů, metody se musejí opravdu
lišit v počtu svých parametrů a/nebo se musejí lišit typy alespoň v jedné dvojici vzájemně si odpovídajících parametrů
►
Zakrytí metody – method hiding Potomek definuje metodou se stejnou signaturou jako rodič, avšak rodičovská metoda zůstává nadále dostupná ● Zakrytí se v Javě uplatní pouze u statických metod ● K zakryté rodičovské metodě se dostanu vhodnou kvalifikací ● Statická metoda nesmí zakrýt instanční metodu
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
5 z 71
►
Přebití metody – method overriding Potomek definuje vlastní verzi zděděné virtuální metody; tato metoda musí mít signaturu kompatibilní s přebitou metodou ● Přebití se uplatní pouze u virtuálních metod
• Virtuální metoda je taková, jejíž implementace a začlenění do programu umožňuje její přebití ● Chování je stejné jako v kartách: metoda potomka přebije rodičovskou, takže tato se neuplatní, i když nadále existuje nezměněná ● Typ parametrů a návratové hodnoty musí být přiřaditelný do typu deklarovaného rodičem, takže typ v přebíjející metodě může být potomkem typu odpovídajícího parametru (návratové hodnoty) v metodě přebité ►
V souvislosti s přebíjením metod se často hovoří o časné a pozdní vazbě ● Časná vazba označuje situaci, kdy překladač již při překladu ví,
která metoda bude volána a může je proto v kódu volat přímo, anebo dokonce nahradit volání metody kopií jejího těla (in-lined code) ● Pozdní vazba označuje stav, kdy se to, která konkrétní metoda bude volána, zjistí až za běhu, a proto je nutné použít nepřímé metody volání Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
6 z 71
►
Předefinování metody – method redefining Dochází ke změně kódu metody prostřednictvím nástrojů pro instrumentaci kódu ● Tyto nástroje mohou změnit definici třídy (a tím i jejich metod)
v průběhu zavádění třídy instancí třídy ClassLoader ● Prostřednictvím instrumentace se může teoreticky změnit signatura metody, nicméně v zájmu konzistence s ostatním kódem se tak nečiní, protože jinak by bylo třeba upravit i další kód ● Někdy se tímto termínem označuje změna definice metody v důsledku nějaké refaktorace ►
Přepsání metody – method rewriting Dochází ke změně kódu metody v důsledku refaktorace či oprav chybného kódu ● Po přepsání metody je, a rozdíl od předchozích operací,
§
nutný nový překlad dané třídy. ● Při přepsání metody se může změnit její signatura
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
7 z 71
2. Funkční interfejsy a lambda výrazy Obsah 2.1 Funkční interfejs 2.1.1 Anotace @FunctionalInterface 2.1.2 Funkční interfejsy v balíčku java.util.function 2.1.3 Důležité funkční interfejsy z jiných balíčků 2.2 Lambda-výrazy 2.2.1 Syntaktická pravidla 2.2.2 Použití v programu 2.2.3 Lambda-výraz jako parametr 2.2.4 Metody jako hodnoty lambda-výrazů 2.2.5 Metoda volající zadanou metodu 2.2.6 Použití metody report(…) 2.2.7 Využití konstruktoru jako tovární metody 2.3 Další syntaktická a sémantická pravidla 2.3.1 Použití this a super v lambda-výrazech 2.3.2 Lambda-výrazy × anonymní třídy 2.3.3 Lambda-výrazy × uzávěry
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
8 z 71
2.1 Funkční interfejs ►
Dosud jediná vyčleněná kategorie: značkovací interfejsy (tagged interfaces) ● Nedeklarují žádnou abstraktní metodu (deklarují pouze kontrakt),
a proto nevyžadují implementaci nějaké metody
►
Nová kategorie: funkční interfejsy (function interfaces) ● Deklarují právě jednu abstraktní metodu
(tj. vyžadující implementaci právě jedné metody); ● Neabstraktních metod může mít kolik chce ● Pokud interfejs přebije kontrakt některé z metod zděděných od třídy Object, není to deklarace abstraktní metody ● Nepočítají se ani statické metody a metody s implicitní implementací ►
Ve standardní knihovně byla takovýchto interfejsů řada, ale doposud byly především jednoúčelové (Comparable, Runnable, ...)
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
9 z 71
2.1.1 Anotace @FunctionalInterface ►
Rozhodneme-li se definovat funkční interfejs, je vhodné anotovat daný typ anotací @FunctionalInterface ● Anotace není povinná, ale poskytuje překladači informaci,
že má u daného datového typu zkontrolovat, že se opravdu jedná o interfejs s jedinou abstraktní metodou
►
Ve standardní knihovně je 231 funkčních interfejsů, přičemž pouze 57 z nich je takto anotováno
►
Program javadoc při vytváření dokumentace nezávisle na anotaci zjistí, zda právě vytváří dokumentaci funkčního interfejsu, a pokud ano, doplní do záhlaví dokumentační stránky zprávu ● Functional Interface:
This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
10 z 71
2.1.2 Funkční interfejsy v balíčku java.util.function ►
Nová verze knihovny přináší speciální balíček java.util.function, definující celou řadu obecných funkčních interfejsů
►
V balíčku je 43 interfejsů rozdělených do několika skupin: ● Metody interfejsů ze skupiny Consumer jsou čistí konzumenti. ● ● ●
●
●
Zpracují své parametry, ale nic nevrací. Metody interfejsů ze skupiny Supplier jsou naopak čistí producenti. Nepotřebují žádné parametry a vracejí hodnotu zadaného typu. Metody interfejsů ze skupiny Function jsou klasické funkce: zpracují svůj parametr a vrátí funkční hodnotu. Metody interfejsů ze skupiny UnaryOperator představují unární operátory. Jsou definovány jako potomci funkcí, jejichž návratová hodnota je stejného typu jako jejich parametr. Metody interfejsů ze skupiny BinaryOperator představují binární operátory. Jsou také definovány jako potomci funkcí, tentokrát funkcí se dvěma parametry přičemž typy obou parametrů i funkční hodnoty jsou shodné. Metody interfejsů ze skupiny Predicate slouží k získání logické hodnoty odvozené z hodnoty jejich parametru.
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
11 z 71
//Producenti – žádné parametry, jen vracejí hodnotu Supplier T get() BooleanSupplier boolean getAsBoolean() DoubleSupplier double getAsDouble() IntSupplier int getAsInt() LongSupplier long getAsLong() //Konzumenti – Zpracují parametry, nic nevracejí //Jednoparametričtí Consumer void accept(T DoubleConsumer void accept(double IntConsumer void accept(int LongConsumer void accept(long //Dvouparametričtí BiConsumer ObjDoubleConsumer ObjIntConsumer ObjLongConsumer
void void void void
accept(T accept(T accept(T accept(T
t, t, t, t,
value) value) value) value)
U double int long
value) value) value) value)
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
12 z 71
//Funkce – zpracují //Jednoparametrikcé Function
parametr (parametry) a vrátí hodnotu vracející objekt R> R apply(T value) R> R apply(double value) R> R apply(int value) R> R apply(long value)
// Jednoparametrikcé vracející hodnotu primitivního typu ToIntFunction int applyAsInt (T ToDoubleFunction double applyAsDouble(T ToLongFunction long applyAsLong (T DoubleToIntFunction int applyAsInt (double DoubleToLongFunction long applyAsLong (double IntToDoubleFunction double applyAsDouble(int IntToLongFunction long applyAsLong (int LongToDoubleFunction double applyAsDouble(long LongToIntFunction int applyAsInt (long //Dvouparametrikcé BiFunction
U, U, U, U,
R> R> R> R>
R double int long
apply (T applyAsDouble(T applyAsInt (T applyAsLong (T
t, t, t, t,
value) value) value) value) value) value) value) value) value) U U U U
u) u) u) u)
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
13 z 71
//Unární operátory UnaryOperator DoubleUnaryOperator IntUnaryOperator LongUnaryOperator
T double int long
apply (T applyAsDouble(double applyAsInt (int applyAsLong (long
operand) operand) operand) operand)
//Binární operátory BinaryOperator DoubleBinaryOperator IntBinaryOperator LongBinaryOperator
T double int long
apply (T applyAsDouble(double applyAsInt (int applyAsLong (long
left, left, left, left,
//Predikáty – metody vracející logickou Predicate boolean DoublePredicate boolean IntPredicate boolean LongPredicate boolean BiPredicate
hodnotu test(T test(double test(int test(long
T double int long
right) right) right) right)
value) value) value) value)
boolean test(T t, U u)
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
14 z 71
2.1.3 Důležité funkční interfejsy z jiných balíčků //Balíček: java.awt.event ActionListener
void actionPerformed(ActionEvent e)
//Balíček: java.io Closeable FileFilter FilenameFilter Flushable
void close() boolean accept(File pathname) boolean accept(File dir, String name) void flush()
//Balíček: java.lang AutoCloseable Comparable Iterable Runnable
void close() int compareTo(T o) Iterator iterator() void run()
//Balíček: java.util. Comparator Observer
boolean equals(Object obj) void update(Observable o, Object arg)
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
15 z 71
2.2 Lambda-výrazy ►
Lambda-výrazy představují způsob, jak definovat jako objekt nějakou část kódu, kterou bychom v jiné části programu rádi použili
►
Lambda-výraz můžeme uložit do proměnné funkčního typu a předávat metodám jako hodnotu parametru tohoto typu
►
Překladač definuje lambda-výraz jako instanci funkčního interfejsu, jehož metoda má odpovídající parametry a vrací hodnotu odpovídajícího typu (příslušný převod překladač zařídí)
►
Lambda výraz se definuje některým z následujících způsobů: (parametry) parametr (parametry) parametr
-> -> -> ->
{ příkazy } { příkazy } výraz výraz
//Obecný tvar //Jediný parametr //Tělo tvoří pouze vyhodnocovaný výraz //Jediný parametr + pouze výraz
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
16 z 71
2.2.1 Syntaktická pravidla ►
Je-li vlevo jediný parametr, nemusí se dávat do kulatých závorek; není-li žádný nebo je-li jich více, závorky jsou potřeba
►
Dokáže-li si překladač odvodit typ parametru, nemusí se uvádět
►
Pokud se vpravo pouze vyhodnocuje nějaký výraz, nemusí se dávat do složených závorek.
►
Z lokálních proměnných lze v lambda výrazu použít pouze tzv. efektivní lokální konstanty metody, v níž je výraz definován, tj.: ● Klasické konstanty (tj. proměnné definované s modifikátorem final) ● Proměnné, které by překladač jako konstanty akceptoval
(jakmile je jim přiřazena nějaká hodnota, už se nemění).
►
Pro atributy žádná omezení na konstanty neplatí
►
Potřebujeme-li v lambda-výrazu používat lokální proměnnou hodnotu, musím ji zabalit do konstantního „mutable“ objektu
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
17 z 71
2.2.2 Použití v programu ►
Světlo schopné nezávislého blikání použitelné např. pro blinkr auta – použijeme návrhový vzor Služebník
/*************************************************************************** * Světlo zadaný počet krát na zabliká, tj. * na zadaný počet milisekund se rozsvítí a na stejnou dobu zhasne. * Akce probíhá na pozadí, takže nijak neovlivňuje běh okolního programu. * * @param times Kolikrát světlo blikne * @param period Perioda blikání zadaná v milisekundách */ public void blink(int times, int period) { Runnable action = () -> { switchOn(); IO.pause(period/2); switchOff(); IO.pause(period/2); }; new Repeater().repeat(times, action); } Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
18 z 71
►
Doplnění kontroly, zda nespouštíme blikání světla, které již bliká
private volatile Repeater repeater = null; public boolean isBlinking() { return repeater != null; } public void blink(int times, int period) { assert (! isBlinking()) : //Abych mohl spustit blikání, nesmí blikat "Blikání bylo spuštěno před ukončením předchozího"; Runnable action = () -> { switchOn(); IO.pause(period/2); switchOff(); IO.pause(period/2); }; new Repeater().repeat(times, action); }
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
19 z 71
2.2.3 Lambda-výraz jako parametr ►
Předchozí program ale není zcela funkční, protože blikající světlo ale neumí zjistit, jestli stále bliká, anebo již doblikalo
►
Problém můžeme vyřešit tak, že opětovně použijeme návrhový vzor posluchač a přihlásíme se u opakovače jako čekatelé na ukončení opakování
►
Přihlašujeme se tak, že jako další parametr metody spouštějící opakované blikání, zadáme metodu, která se má spustit po skončení opakování
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
20 z 71
public void blink(int times, int period, Runnable finished) { assert isBlinking() : "Blikání bylo spuštěno před ukončením předchozího"; repeater = new Repeater(); Runnable action = () -> { switchOn(); IO.pause(period/2); switchOff(); IO.pause(period/2); }; Runnable blinkingFinished = () -> { repeater = null; finished.run(); }; repeater.repeat(times, action, blinkingFinished); }
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
21 z 71
2.2.4 Metody jako hodnoty lambda-výrazů ►
Jako hodnotu lambda-výrazu lze definovat i existující metodu:
Cls ::mtd inst::mtd Cls ::new
//printMtd = IO::inform; //printMtd = System.out::println; //factMtd = MyClass::new;
►
Stačí název metody, protože doplnění signatury o typy parametrů vyřeší překladač podle okamžité situace
►
Speciality: ● int[]::new označuje jednoparametrický konstruktor,
jehož parametrem je délka zkonstruovaného pole; je to ekvivalent lambda-výrazu x −> int[x]
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
22 z 71
2.2.5 Metoda volající zadanou metodu //Kód ve třídě test\cz.pecinovsky.en_cz.uoa2_j8.aha.LambdaDemo /*************************************************************************** * Metoda má na starosti provedení zadané akce * spolu s oznámením jejího nastartování a ukončení. * * @param Typ parametru konzumenta * @param description Popis druhu demonstrovaného způsobu definice metody * @param definition Textová podoba definice metody konzumenta * @param consumer Popsaným způsobem definovaný konzument * @param argument Argument konzumenta */ private static void report(String description, String definition, Consumer consumer, T argument) { IO.inform(description + "\n\nBudeme volat metodu definovanou:\n\n" + definition); consumer.accept(argument); IO.inform("Ukončena metoda definovaná:\n\n" + definition); } Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
23 z 71
2.2.6 Použití metody report(…) //Kód ve třídě test\cz.pecinovsky.en_cz.uoa2_j8.aha.LambdaDemo @Test public void testConsumers() { String da; String dm; Consumer<String> cs; Consumer cp;
//Popis demonstrované akce //Stringová podoba definice metody //Konzument textových řetězců //Konzument zobrazitelných objektů
da = "Přiřazení statické metody lambda-výrazem"; cs = (s) -> IO.inform(s); //Metoda třídy IO dm = "(s) -> IO.inform(s)"; report(da, dm, cs, dm); da = "Přímé přiřazení statické metody"; cs = IO::inform; dm = "IO::inform"; report(da, dm, cs, dm); Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
24 z 71
da = "Přiřazení obecné metody instance lambda-výrazem"; cs = (s) -> System.out.println(s); //Metoda instance System.out dm = "(s) -> System.out.println(s)"; report(da, dm, cs, dm); da = "Přímé přiřazení metody konkrétní instance"; cs = System.out::println; dm = "System.out::println"; report(da, dm, cs, dm); CanvasManager CM = CanvasManager.getInstance(); da = "Přiřazení metody konkrétní instance lambda-výrazem"; cp = (p) -> p.show(); dm = "(p) -> p.show()"; report(da, dm, cp, new Rectangle());
}
da = "Přímé přiřazení metody konkrétní instance"; cp = IPaintable::show; dm = "IPaintable::show"; report(da, dm, cp, new Ellipse());
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
25 z 71
2.2.7 Využití konstruktoru jako tovární metody ►
Světlo musí být schopno vytvářet své kopie – máme dvě možnosti: ● Budeme chtít, aby tovární metoda vytvářila kopírovatelnou žárovku ● Využijeme toho, že máme tovární metodu, a použijeme ji při vytváření kopie
►
Zvolíme-li druhou variantu (je jednodušší), vznikne však problém s typem ukládaného objektu (továrny) ● Překladač neakceptuje deklaraci
private final Supplier bulbFactory; ● Akceptuje však žolíkovou variantu: private Supplier bulbFactory; ►
Žolíková varianta sice zkomplikuje zadávání továrního objektu konstruktoru v těle metody copy(), ale to se dá vyřešit ● přetypováním na surový typ + ● potlačením vypisování varovných zpráv
o vypnutí kontroly typových parametrů v těle této metody
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
26 z 71
public Light(int x, int y, int size, NamedColor color) { this(x, y, size, color, Ellipse::new); } public //Kombinobaný typový parametr Light(int x, int y, int size, NamedColor color, Supplier bulbFactory) //V parametru obdržíme tovární metodu { TC bulb = bulbFactory.get(); this.color this.bulbFactory this.changeableBulb this.monocolorBulb
}
= = = =
color; bulbFactory; bulb; bulb;
changeableBulb.setPosition(x, y); changeableBulb.setSize(size, size); monocolorBulb .setColor(color);
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
27 z 71
/** Tovární objekt na výrobu žárovek. */ private final Supplier bulbFactory; @Override @SuppressWarnings("unchecked") public Light copy() { Position position = changeableBulb.getPosition(); Light copy = new Light(position.x, position.y, changeableBulb.getSize().width, color, (Supplier)bulbFactory); //Je třeba přetypovat na surový typ return copy; }
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
28 z 71
2.3 Další syntaktická a sémantická pravidla ►
Použití this a super v lambda-výrazech
►
Lambda-výrazy × anonymní třídy
►
Lambda-výrazy × uzávěry
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
29 z 71
2.3.1 Použití this a super v lambda-výrazech ►
Proměnná this použitá v lambda-výrazu se odkazuje na stejnojmenný skrytý parametr instanční metody, v níž je lambda-výraz definován
►
Atribut super použitá v lambda-výrazu se odkazuje na stejnojmenný skrytý atribut instance, v jejíž metodě je lambda-výraz definován
►
V lambda-výrazech definovaných ve vnitřních třídách se tak můžete odvolat na this a super příslušné vnější třídy odpovídající kvalifikací – např.: EnclosingClass.this::method nebo EnclosingClass.super::method
►
=> lambda-výraz se nemůže odkázat na svůj this ani super
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
30 z 71
2.3.2 Lambda-výrazy × anonymní třídy ►
Lambda-výrazy je vhodné použít v situacích, v nichž jsme dříve používali anonymní třídy a naopak, instance anonymních tříd lze použít místo lambda-výrazů; přesto to není totéž
►
Na rozdíl od jiných jazyků Java nezavádí speciální typ funkcí (např.: (String, String) → int)); místo toho umožňuje uložit lambda-výraz do proměnné, jejímž typem je nějaký funkční interfejs
►
Přetypování na instanci funkčního interfejsu je také to jediné, co můžeme s lambda-výrazem udělat
►
Lambda-výraz nelze uložit do proměnné typu Object, protože třída Object není funkční interfejs
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
31 z 71
2.3.3 Lambda-výrazy × uzávěry ►
Lambda-výraz má tři komponenty: ● Blok kódu ● Parametry ● Volné proměnné (free variables),
což jsou použité lokální proměnné okolního kódu
►
Dvojice [Blok kódu, Volné proměnné] bývá označována jako uzávěr => lambda-výrazy v Javě fungují jako uzávěry
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
32 z 71
3. Nová koncepce konstrukce interface Obsah 3.1 Interfejs a implementace 3.1.1 Jednoduchý diamantový problém 3.1.2 Složitější diamantový problém 3.1.3 Náhled přes konstanty 3.1.4 Ukázka implicitní implementace 3.2 Speciální situace a omezení 3.3 Důsledky pro návrh architektury 3.3.1 Použití ve standardní knihovně 3.3.2 Ukázka použití u funkčních interfejsů 3.4 Statické metody 3.4.1 Důsledky pro návrh architektury Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
33 z 71
3.1 Interfejs a implementace ►
Java zavedla konstrukci interface jako programovou reprezentaci rozhraní
►
Jeden z účelů byl obejití problémů při používání násobného dědění ● Problémy s násobnými rodiči vznikají zejména
v souvislosti s násobným děděním implementace ● Budeme-li dědit pouze rozhraní, tj. pouze sliby, problémy v podstatě odpadají ● Navíc se ukázalo, že zavedením této konstrukce se otevírá celá řada nových možností v návrhu architektury ►
Autoři některých jazyků následně ukázali, že hlavní příčinou tzv. diamantového problému je v podstatě pouze násobné dědění dat => zakážeme-li dědění dat, problémy nebudou
►
Java proto ve verzi 8 umožňuje v definicích interfejsů uvádět i implicitní implementace metod
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
34 z 71
3.1.1 Jednoduchý diamantový problém ►
Při násobném dědění od společného předka dvou rodičů je třeba určit, jestli se data tohoto předka budou dědit jednou nebo dvakrát
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
35 z 71
3.1.2 Složitější diamantový problém
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
36 z 71
3.1.3 Náhled přes konstanty ►
Java umožňuje definovat v interfejsu statické konstanty
►
Tyto konstanty nepředstavují konkrétní uložená data, jsou to především názvy, za něž se dosazují konkrétní hodnoty
►
S nástupem lambda-výrazů lze do těchto konstant přiřazovat i kód
►
Vzhledem k tomu, že metoda je pouze jinak zapsaný konstantní lambda-výraz, je jenom logické, že v Javě 8 lze v interfejsu definovat i metody
►
Instanční metody s definovaným tělem se v Javě označují modifikátorem default , třídní metody se stejně jako ve třídách označují modifikátorem static
►
Implicitní verze metod nesmí přebíjet žádnou metodu předka
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
37 z 71
3.1.4 Ukázka implicitní implementace ►
Definujeme metody, které by asi všichni definovali stejně
public interface IMovable extends IPaintable { public Position getPosition(); public void setPosition(int x, int y);
}
public default void setPosition(Position position) { setPosition(position.x, position.y); }
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
38 z 71
3.2 Speciální situace a omezení ►
Objeví-li se ve stromu dědičnosti více metod se stejnou signaturou, platí následující pravidla: 1. Třída vždy vyhrává. Definuje-li rodičovská třída metodu se stejnou signaturou, jakou má implicitně definovaná metoda implementovaného interfejsu, metoda interfejsu je ignorována 2. Definují-li dva implementované interfejsy metody se shodnou signaturou, musí implementující třída vyřešit konflikt tím, že definuje vlastní verzi této metody, která přebije verze zděděné od implementovaných interfejsů, a to nezávisle na tom, jestli některá z nich (či obě) má implicitní definici. (Jinými slovy: třída se chová stejně jako před Javou 8.)
►
Z předchozího vyplývá, že nemá smysl, aby interfejs definoval implicitní verze metod zděděných od Object, a proto tyto metody ani přebít nesmí
►
Interfejs však může přebít implicitní metodu svého předka
►
Interfejs nyní může obsahovat metodu main a vystupovat tak v roli hlavní třídy aplikace
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
39 z 71
3.3 Důsledky pro návrh architektury ►
Při používání klasicky navrženého interfejsu jsme museli tento interfejs velmi pečlivě navrhnout, protože jakékoliv další úpravy publikovaného interfejsu jsou zakázané
►
Při povolení implicitních implementací deklarovaných metod můžeme dodatečně rozšiřovat funkcionalitu tříd implementujících dané interfejsy, aniž bychom museli měnit jejich kód
►
Zavedení interfejsů s implicitní implementací nahrazuje v některých případech použití některých návrhových vzorů ● V řadě případů umožňuje obejít návrhový vzor Adapter
(např. při návrhu posluchačů v java.awt) ● V dalších případech umožňuje se obejít bez použití návrhového vzoru Návštěvník (Visitor)
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
40 z 71
3.3.1 Použití ve standardní knihovně ►
Převážná většina interfejsů používá implicitní implementaci, v mnohých je implementovaných metod víc než těch abstraktních
public interface Iterable { Iterator iterator(); default void forEach(Consumer action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } }
}
default Spliterator spliterator() { return Spliterators.spliteratorUnknownSize(iterator(), 0); }
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
41 z 71
3.3.2 Ukázka použití u funkčních interfejsů @FunctionalInterface public interface BiFunction { //Povinná abstraktní metoda R apply(T t, U u);
}
//Metoda s implicitní definicí rozšiřující funkcionalitu definovaného typu //Parametrem je funkce, akceptující jako parametr návratový typ //abstraktní metody definovaného funkčního typu default BiFunction andThen(Function after) { Objects.requireNonNull(after); return (T t, U u) -> after.apply(apply(t, u)); }
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
42 z 71
3.4 Statické metody ►
Java 8 dovoluje v interfejsu definovat statické metody
@FunctionalInterface public interface BinaryOperator extends BiFunction { //Vrátí operátor vracející komparátorem určenou menší ze zadaných hodnot public static BinaryOperator minBy(Comparator comparator) { Objects.requireNonNull(comparator); return (a, b) -> comparator.compare(a, b) <= 0 ? a : b; }
}
//Vrátí operátor vracející komparátorem určenou větší ze zadaných hodnot public static BinaryOperator maxBy(Comparator comparator) { Objects.requireNonNull(comparator); return (a, b) -> comparator.compare(a, b) >= 0 ? a : b; }
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
43 z 71
@FunctionalInterface public interface Function { R apply(T t); default Function compose(Function before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); } default Function andThen(Function after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); }
}
static Function identity() { return t -> t; }
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
44 z 71
3.4.1 Důsledky pro návrh architektury ►
Možnost definovat statické metody v interfejsech osvobozuje od nutnosti definovat knihovní třídy pro skupiny tříd implementujících daný interfejs ● Např. řadu metod třídy Collections by bylo možno definovat
jako statické metody interfejsu Collection
►
§
Mnohé z knihovních metod by navíc bylo lze definovat jako instanční (např. shuffle(List), sort(List) apod.)
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
45 z 71
4. Datový typ java.util.Optional Obsah 4.1 Základní princip 4.2 Ekvivalenty jednoduchých operací 4.3 Získání prvotní hodnoty 4.4 Definované metody
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
46 z 71
4.1 Základní princip ►
Velmi často pracujeme s proměnnými, jejichž hodnotou může být prázdný odkaz null; kód pak bývá prokládán neustálými testy na „nullovost“ proměnné
►
Některé jazyky řeší tento problém zavedením speciálních operátorů ● Groovy – safe navigation operator (Elvis operator) ?.
(x ?. y) == ((x == null) ? null : x.y)
►
Java nezavádí nový operátor, ale místo toho rozšiřuje knihovnu o generický datový typ Optional ● Výhodou je, že takovýto typ bylo možno dodefinovat i ve starších verzích,
takže pokud někdo používal něco obdobného, nebude muset měnit zvyky ● Další výhodou je to, že takto můžeme pro takto definované objekty definovat celou řadu pomocných služebních metod a k nim i několik továrních metod
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
47 z 71
4.2 Ekvivalenty jednoduchých operací ►
Použití hodnoty Optional optional = something; optional.get().method(); je ekvivalentní klasickému T object = something. object.method() Jediným rozdílem je, že v typu vyhozené výjimky – v prvním případě bude vyhozena NoSuchElementException, v druhém NullPointerException
►
Opatrné použití hodnoty
if (optional.isPresent()) { optional.get().method(); }
je pouze komplikovanější variantou klasického if (object != null) { object.method(); }
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
48 z 71
4.3 Získání prvotní hodnoty ►
public static Optional empty() Získání prázdné hodnoty
►
public static Optional of(T value) Získání instance obalující zadanou nenullovou hodnotu; je-li parametr null, vyhodí NullPointerException
►
public static Optional ofNullable(T value) Získání instance obalující zadanou hodnotu; je-li parametr null, vrátí Optional.empty()
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
49 z 71
4.4 Definované metody ►
Podmíněné volání metody public void ifPresent(Consumer consumer) optional.ifPresent(consumer); je ekvivalentní klasickému if (value != null) { consumer.accept(value); }
►
Filtrování hodnoty public Optional filter(Predicate predicate) optional = optional.filter(predicate); je ekvivalentní klasickému optional = (optional.isPresent) ? (predicate(optional.get()) ? optional : Optional.empty() : optional;
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
50 z 71
►
Mapování hodnoty na hodnotu public Optional map(Function mapper) newOptional = optional.map(mapper); je ekvivalentní klasickému newOptional = optional.isPresent() ? Optional.ofNullable(mapper.apply(optional.get())) : Optional.empty(); Oproti filtru se zde vyrábí nová instance pomocí empty()
►
Zplošťující mapování na hodnotu (aby nevznikl Optional> publicOptional flatMap(Function> mapper) newOptional = optional.flatMap(mapper); je ekvivalentní klasickému newOptional = optional.isPresent() ? Objects.requireNonNull(mapper.apply(optional.get())) : Optional.empty(); Metoda mapper(Optional) musí vrátit nenullový Optional
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
51 z 71
►
Přiřazení implicitní hodnoty v případě nullovosti odkazu public T orElse(T other) value = optional.orElse(other); je ekvivalentní klasickému value = optional.isPresent() ? optional.get() : other; //neboli value = (oValue != null) ? oValue : other;
►
Přiřazení spočtené hodnoty v případě nullovosti odkazu public T orElseGet(Supplier other) value = optional.orElseGet(other); je ekvivalentní klasickému value = optional.isPresent() ? optional.get() : other.get(); //neboli value = (oValue != null) ? oValue : other.get();
§ Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
52 z 71
5. Datové proudy Obsah 5.1 Seznámení 5.1.1 Motivace 5.1.2 Obecná charakteristika 5.1.3 Terminologie 5.1.4 Změna používaných iterátorů 5.1.5 Analogie s výrobní linkou 5.1.6 Odchylky od kolekcí a polí 5.1.7 Typy operací s daty v proudu: 5.1.8 Vztah operací k proudům 5.2 Operace s daty v proudu 5.2.1 Ukázka 5.2.2 Filtrování obsahu proudu 5.2.3 Řazení objektů v proudu 5.2.4 Konverze prvků v proudu 5.3 Možnosti získání proudu 5.3.1 Tovární metody pro generování proudů
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
53 z 71
5.1 Seznámení 5.1.1 Motivace ►
Klasický postup: Opakovaně prováděj: požádej kontejner o další uloženou instance, zjisti, jestli patří mezi ty, které je třeba zpracovat, pokud ano tak instanci požadovaným způsobem zpracuj
►
Nevýhody: ● Zbytečně se opakuje podobný kód ● V programu zpracovává jedna položka po druhé
►
Trend: posilovat deklarativní přístup k programování ● Snaha moci vysvětlit co se má dělat a nechat na knihovně jak to udělá ● Umožníme tím specialistům navrhujícím knihovny
optimalizovat řešení – např. pro intenzivní využití paralelismu
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
54 z 71
5.1.2 Obecná charakteristika ►
Knihovny kolekcí a polí jsou staré a těžko modifikovatelné => ● Snaha použít jiné přístupy známé z funkcionálního programování ● Ve FP jsou proudy chápány jako univerzální posloupnosti
postupně zpřístupňovaných dat
►
Java 8: Proud je klíčová abstrakce pro zpracování skupin dat umožňující specifikovat, co se má s daty dělat, a abstrahovat od toho, jak se to bude dělat
►
Většina metod proudů čte data proudu a vrací proud obsahující data příslušné (před)zpracovaná
►
Ukázka – sečti váhy červených udělátek v kolekci widgets int sum = widgets.stream() .filter(b -> b.getColor() == RED) .mapToInt(b -> b.getWeight()) .sum();
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
55 z 71
5.1.3 Terminologie ►
V OOP jsou pod termínem proud často chápány objekty zprostředkovávající přenos dat od zdroje k cíli ● Datové proudy jsou pak většinou spojovány přenosem dat
od zdroje k cíli, přičemž zdrojem či cílem může být program, soubor, datovod (pipe), oblast paměti, objekt, … ● Ve standardní knihovně Javy jsou takto pojímané proudy definovány jako potomci abstraktních tříd InputStream, OutputStream, Reader a Writer ►
Funkcionální programování však tento termín používá pro potenciálně nekonečné seznamy, které však data neuchovávají, ale pouze „o nich ví“. ● Program definuje, jaká data se do proudu zařadí,
resp. jak proud ona data získá ● Nezávisle na tom se definuje, co se těmito daty bude dělat ● Proudy jsou ve své přirozenosti funkcionální, takže nemění data umístěná v proudu – každé „dato“ je za života proudu „navštíveno“ jen jednou ● Většina operací je implementována jako odložené (lazy) Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
56 z 71
5.1.4 Změna používaných iterátorů ►
Při práci s klasickými poli a kolekcemi se používají především externí (sekvenční) iterátory ● Program požádá kolekci o iterátor, který pak v cyklu žádá o další instanci,
s níž provede požadovanou operaci
►
Proud se chová jako objekt využívající interní (dávkové) iterátory provádějící s iterovanými objekty operace definované lambda-výrazy
►
Výhody: ● Z programu zmizí pomocný kód, který měl na starosti řízení iterací,
(tj. popis jak to dělat) a zpřehlední se tak popis akcí, které budou popisovat, co se zpracovávanými objekty udělat ● Objeví-li se v budoucnu nějaké propracovanější techniky paralelizace prováděných činností, není třeba upravovat část programu řešící aplikační logiku, ale stačí pouze vylepšit knihovnu proudů a tím se automaticky zefektivní zpracování programů, které tyto proudy využívají Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
57 z 71
5.1.5 Analogie s výrobní linkou ►
Na počátku je vstup ● V případě výrobního pásu je to sklad dílů,
nebo nějak zabezpečený přísun vstupních surovin od dodavatele ● V případě proudu to bude zdroj dat – většinou nějaký zdrojový kontejner, zdrojem ale může být i generátor, který vytváří data na požádání ►
Pracoviště ● Podél výrobního pásu jsou připravená pracoviště,
na nichž zaškolení dělníci provádějí jednotlivé operace ● V případě proudu nahradíme dělníky metodami (přesněji lambda výrazy), které s přišedším objektem provedou požadovanou operaci a … ● … vypustí jej do dalšího proudu, kde pokračuje k dalšímu pracovišti ►
Výsledky ● Na konci výrobního pásu „vypadne“ hotový výrobek ● Na konci série operací s daty v proudu obdržíme (doufejme )
požadovaný výsledek
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
58 z 71
5.1.6 Odchylky od kolekcí a polí ►
Proudy si neblokují žádnou paměť pro zpracovávaná data; data od někud „přitékají“, „pracoviště“ je zpracuje a pošle dál
►
Ve většině případů zpracování neovlivňuje zdrojová data ● Důležité při hromadném zpracování dat několika procesory,
protože pak se můžeme na jejich hodnoty spolehnout
►
Naplánované průběžné operace se chovají obdobně jako dělníci u pásu: ● Nehrnou se do skladu (kontejneru), aby v něm data zpracovaly, ale:
1. Počkají si, až k nim zpracovávaný objekt po vstupním proudu „přiteče“ 2. Přišedší objekt zpracují 3. Zpracovaný objekt vloží do výstupního proudu a vrátí odkaz na tento proud
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
59 z 71
5.1.7 Typy operací s daty v proudu: ►
Filtrace ● Např. při procházení souborů na disku mne zajímají jen ty s danou příponou
►
Mapování (konverze) ● Z přišedšího objektu mne zajímá jen hodnota jednoho atributu –
do výstupního proudu odchází ona
►
Řazení ● Pro další zpracování potřebuji mít data definovaným způsobem seřazena
►
Souhrny (reduction, folding) ● Na přitékajících datech mne zajímá pouze nějaká souhrnná informace
průměr, součet, …
►
Sdružování (mutable reduction) ● Zpracovaná data vkládám do nějakého kontejneru (kolekce, pole, …)
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
60 z 71
5.1.8 Vztah operací k proudům ►
Vrácený proud nemusí být shodný se vstupním ● Když má operace např. zjistit počet znaků v řetězci,
je vstupním proudem proud textových řetězců (stringů) a výstupním proudem proud celých čísel ● V případě potřeby může mít výstupní proud jinak uspořádaná data než proud vstupní ►
Operace nejsou nijak ovlivněny tím, zda je vstup dat konečný (klasický kontejner), nebo nekonečný ● Data prostě přitékají a co přiteče, to se zpracuje, a čeká se na další
►
Operace nejsou svázány s konkrétním proudem; program může postupně vytvářet různé proudy, na jejichž data pošle danou sadu operací ● Jednou může být zdrojem dat kolekce, jindy generátor apod. ● Operace pouze vědí, že data přijdou, ony je mají zpracovat a někam poslat
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
61 z 71
5.2 Operace s daty v proudu ►
Průběžné (intermediate) ● Převezmou objekt, zpracují jej a pošlou dále po proudu,
případně je předají do jiného proudu ● Vracejí proud, takže je lze jednoduše zřetězit ● Nemusejí vždy vracet svůj proud, ale mohou vracet i jiný => nelze obecně zavolat průběžnou metodu, aniž bychom si zapamatovali proud, který nám vrátí a jehož metodu pak musíme v dalším kroku oslovit ►
Koncové (terminal) ● Zakončují činnost proudu a předávají výsledek okolnímu programu ● Výstupní hodnotou může být kolekce, objekt, ale také nic
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
62 z 71
5.2.1 Ukázka /*************************************************************************** * Zabezpečí, aby každé světlo na zhaslo, bliklo a opět se rozsvítilo. * To, jestli budou světla pracovat postupně nebo všechna najednou, * záleží na typu proudu. */ private void streamBlink(Stream stream, String text) { //Akci, kterou budu chtít zadat vícekrát, si připravím a uložím Consumer pause = (o)->IO.pause(500); //Zapamatuje si proud vrácený poslední průběžnou operací stream = stream //Diktuji operace, které se budou v proudu postupně provádět .peek(Light::switchOff) .peek(pause) .peek(Light::blink) .peek(pause) .peek(Light::switchOn); //Zatím se nic nedělo, dít se začne až při provádění terminální akce long count = stream.count(); IO.inform(text + "\n\nBlikalo: " + count); } Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
63 z 71
//Testy výše uvedené metody @Before public void setUp() { lightXYS = new Light( 0, 0, 100); lightXY = new Light(100, 50); lightXYC = new Light( 50, 100, NamedColor.RED); lightXYSC = new Light(100, 100, 100, NamedColor.GREEN); lightsArr = new Light[] {lightXY, lightXYC, lightXYS, lightXYSC}; lightsCol = Arrays.asList(lightsArr); } @Test public void testGroupBlink() { Stream stream = Arrays.stream(lightsArr); //lightsCol.stream(); streamBlink(stream, "testGroupBlink"); } @Test public void testGroupBlinkParallel(){ Stream stream = lightsCol.parallelStream(); //Arrays.stream(lightsArr).parallel(); streamBlink(stream, "testGroupBlinkParallel"); } Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
64 z 71
►
Pokud bychom nechtěli ukázat, že akce se spouští až při zavolání koncové metody, mohli bychom použít test ve tvaru
private void streamBlinkFE(Stream stream) { //Akci, kterou budu chtít zadat vícekrát, si připravím a uložím Runnable pause = ()->IO.pause(500); //Zapamatuje si proud vrácený poslední průběžnou operací stream.forEach((Light x) -> { x.switchOff(); pause.run(); x.blink(); pause.run(); x.blink(); pause.run(); x.blink(); pause.run(); x.switchOn(); });
} @Test public void testGroupBlinkFE() { Stream stream = lights.stream(); streamBlinkFE(stream); }
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
65 z 71
5.2.2 Filtrování obsahu proudu ►
Obsah proudu můžeme během práce filtrovat použitím metody Stream filter(Predicate predicate)
/************************************************************************** * Začlení světla do paralelního proudu, odfiltruje ta, co nejsou žlutá, * a ta zbylá (tj. ta žlutá) nechá společně zablikat. */ @Test public void testYellowBlink() { Stream stream = lightsCol.parallelStream(); stream = stream.filter((light) -> light.getColor().equals(NamedColor.YELLOW)); streamBlink(stream, "testYellowBlinkD"); }
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
66 z 71
5.2.3 Řazení objektů v proudu ►
K zadání akce přeuspořádavající daný proud slouží metody Stream sorted() Stream sorted(Comparator comparator)
/************************************************************************** * Začlení světla do sériového proudu, seřadí je v něm podle vodorovné * souřadnice a nechá je v tomto pořadí postupně zablikat. */ @Test public void testXSortedBlink() { Stream stream = lights.stream(); stream = stream.sorted((first, second) -> first.getPosition().x - second.getPosition().x); streamBlink(stream); } Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
67 z 71
5.2.4 Konverze prvků v proudu ►
Prvek, který do proudu „nastoupil“, doposud procházel jednotlivými zastávkami, dokud činnost proudu neskončila
►
Proudy však umožňují vyměnit cestou druh prvků, s nimiž pracují prostřednictvím některé z metod: Stream map(Function mapper) DoubleStream mapToDouble(ToDoubleFunction mapper) IntStream mapToInt (ToIntFunction mapper) LongStream mapToLong (ToLongFunction mapper)
public static void createAndDrive(IVehicleFactory vehicleFactory, String directions, Position... positions) { Arrays.stream(positions).parallel() .map(vehicleFactory::newVehicle) //Převede pozice na vozidla .peek((vehicle) -> {CM.add(vehicle);}) .forEach(goInDirections(directions)); } Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
68 z 71
5.3 Možnosti získání proudu ►
Z kolekcí prostřednictvím metod stream() a parallelStream()
►
Z polí prostřednictvím metody Arrays.stream(Object[])
►
Řádky souboru lze získat metodou BufferedReader.lines()
►
Proud souborů na zadané cestě (java.nio.file.Path) lze získat metodami třídy java.nio.file.Files
►
Proud náhodných čísel lze získat metodou Random.ints()
►
Proud položek v ZIP-souboru lze získat metodou JarFile.stream()
►
Proud částí sekvence znaků lze získat voláním statické metody Pattern.splitAsStream(java.lang.CharSequence)
►
Proud indexů nastavených položek lze získat voláním BitSet.stream()
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
69 z 71
5.3.1 Tovární metody pro generování proudů ►
Třída Stream nabízí navíc metody, s jejichž pomocí lze definovat generátory proudů zadaným způsobem vytvořených objektů
►
static Stream of(T... values) Vytvoří proud zadaných hodnot
►
static Stream iterate(T seed, UnaryOperator f) Vytvoří nekonečný seřazený proud, jehož počáteční prvek je tvořen semenem seed a následující prvky jsou vytvářeny aplikací operátoru f na předchozí prvek
►
static Stream generate(Supplier s) Vytvoří nekonečný proud, jehož prvky jsou výsledkem volání s
►
IntStream range(int, int) Třída IntStream umí vytvořit proud celých čísel ze zadaného intervalu
Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
70 z 71
6. KONEC
Děkuji za pozornost
Rudolf Pecinovský [email protected] Copyright © Rudolf Pecinovský, Soubor: PR_Funkcionalni_konstrukce_v_Jave_8.doc, verze 1.00.2413, uloženo út 4.2.2014 – 07:22
71 z 71