Lambda-výrazy a metody implementované v interfejsu Pecinovský
[email protected] Copyright © Rudolf Pecinovský, Soubor: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
1 z 54
Obsah 1. Terminologie 2. Funkční interfejsy a lambda výrazy 3. Nová koncepce konstrukce interface 4. KONEC
Copyright © Rudolf Pecinovský, Soubor: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
2 z 54
1. Terminologie Obsah 1.1
Rozhraní × Interfejs
1.2
Přetížení × zakrytí × přebití × předefinování 1.2.1 Přetížení metody – method overloading 1.2.2 Zakrytí metody – method hiding 1.2.3 Přebití metody – method overriding 1.2.4 Časná a pozdní vazba – early and late binding 1.2.5 VMT 1.2.6 Předefinování metody – method redefining 1.2.7 Přepsání metody – method rewriting
Copyright © Rudolf Pecinovský, Soubor: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
3 z 54
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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
4 z 54
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ů
►
Zakrytí metody – method hiding Potomek definuje metodou se stejnou signaturou jako rodič, avšak rodičovská metoda zůstává nadále dostupná
►
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ředefinování metody – method redefining Dochází ke změně kódu metody prostřednictvím nástrojů pro instrumentaci kódu
►
Přepsání metody – method rewriting Dochází ke změně kódu metody v důsledku refaktorace či oprav chybného kódu
Copyright © Rudolf Pecinovský, Soubor: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
5 z 54
1.2.1 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ů
Copyright © Rudolf Pecinovský, Soubor: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
6 z 54
1.2.2 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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
7 z 54
1.2.3 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á ● Typy parametrů a návratové hodnoty mohou být stejné, ale nemusí; protože proměnná předka akceptuje odkaz na instanci potomka • Typ parametrů musí akceptovat proměnné typu deklarovaného rodičem => typ parametru v přebíjející metodě může být rodičem typu odpovídajícího parametru v metodě přebité • Typ návratové hodnoty být přiřaditelný do proměnné typu deklarovaného rodičem => typ návratové hodnoty přebíjející metody může být potomkem typu návratové hodnoty přebíjené metody
Copyright © Rudolf Pecinovský, Soubor: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
8 z 54
1.2.4 Časná a pozdní vazba – early and late binding ►
V souvislosti s přebíjením metod se často hovoří o časné a pozdní vazbě (early and late binding) ● Č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í ►
Pozdní vazba se většinou realizuje prostřednictvím tabulky virtuálních metod (VMT – virtual method table) ● VMT obsahuje adresy na kódy virtuálních metod ● Překladač o každé metodě ví, kolikátá je ve VMT ● Dceřiné typy přebírají VMT svého rodiče a mohu do něj přidat další položky ● Přebije-li dceřiná metoda rodičovskou, překladač zařídí,
aby příslušný odkaz ve VMT odkazoval na dceřinou metodu
Copyright © Rudolf Pecinovský, Soubor: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
9 z 54
1.2.5 VMT
Copyright © Rudolf Pecinovský, Soubor: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
10 z 54
1.2.6 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
Copyright © Rudolf Pecinovský, Soubor: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
11 z 54
1.2.7 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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
12 z 54
2. Funkční interfejsy a lambda výrazy Obsah 2.1
Návrhový vzor Příkaz (Command)
2.2
Funkční interfejs 2.2.1 Anotace @FunctionalInterface 2.2.2 Funkční interfejsy v balíčku java.util.function 2.2.3 Důležité funkční interfejsy z jiných balíčků
2.3
Lambda-výrazy 2.3.1 Syntaktická pravidla 2.3.2 Použití v programu 2.3.3 Lambda-výraz jako parametr – teorie 2.3.4 Lambda-výraz jako parametr – příklad 2.3.5 Rozšířená verze příkladu
2.3.6 Metody jako hodnoty lambda-výrazů 2.3.7 Příklad 2.3.8 Metoda volající zadanou metodu 2.3.9 Použití metody report(…) 2.3.10 Využití konstruktoru jako tovární metody 2.4
Další syntaktická a sémantická pravidla 2.4.1 Použití this a super v lambda-výrazech 2.4.2 Lambda-výrazy × anonymní třídy 2.4.3 Lambda-výrazy × uzávěry
Copyright © Rudolf Pecinovský, Soubor: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
13 z 54
2.1 Návrhový vzor Příkaz (Command) ►
Inverzní vzor ke vzoru Služebník: Máme objekt, který něco umí, ale nezná nějakou klíčovou akci – tu mu předáme prostřednictvím objektu předaného v parametru ● Program zabalí akci do objektu, který předá metodě v parametru
/** Světlo zadaný počet krát na zabliká. */ public void blink(int times, int period) { class MyRunnable extends Runnable { public run() { switchOn(); IO.pause(period/2); switchOff(); IO.pause(period/2); } }; Runnable action = new MyRunnable(); //Metoda repeat očekává v prvním parametru počet opakování //a v druhém parametru parametru akci, která se má opakovat new Repeater().repeat(times, action); } Copyright © Rudolf Pecinovský, Soubor: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
14 z 54
2.2 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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
15 z 54
2.2.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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
16 z 54
2.2.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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
17 z 54
//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
Copyright © Rudolf Pecinovský, Soubor: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
value) value) value) value)
18 z 54
//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 ToIntFunction int ToDoubleFunction double ToLongFunction long DoubleToIntFunction int DoubleToLongFunction long IntToDoubleFunction double IntToLongFunction long LongToDoubleFunction double LongToIntFunction int
primitivního typu applyAsInt (T applyAsDouble(T applyAsLong (T applyAsInt (double applyAsLong (double applyAsDouble(int applyAsLong (int applyAsDouble(long applyAsInt (long
//Dvouparametrikcé BiFunction
apply (T applyAsDouble(T applyAsInt (T applyAsLong (T
U, U, U, U,
R> R> R> R>
R double int long
t, t, t, t,
Copyright © Rudolf Pecinovský, Soubor: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
value) value) value) value) value) value) value) value) value) U U U U
u) u) u) u) 19 z 54
//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,
T double int long
right) right) right) right)
//Predikáty – metody vracející logickou hodnotu Predicate boolean test(T value) DoublePredicate boolean test(double value) IntPredicate boolean test(int value) LongPredicate boolean test(long value) BiPredicate
boolean test(T t, U u)
Copyright © Rudolf Pecinovský, Soubor: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
20 z 54
2.2.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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
21 z 54
2.3 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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
22 z 54
2.3.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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
23 z 54
2.3.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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
24 z 54
2.3.3 Lambda-výraz jako parametr – teorie ►
Předchozí program ale není zcela funkční, protože blikající světlo 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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
25 z 54
2.3.4 Lambda-výraz jako parametr – příklad 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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
26 z 54
2.3.5 Rozšířená verze příkladu private Repeater repeater = null; //** Zjistí, jestli dané světlo právě bliká. */ public boolean isBlinking() { return repeater != null; } /** Ukončí blikání a zapomene odkaz na opakovač. */ public void stopBlinking() { assert (repeater != null) : "Pokus o ukončení neexistujícího blikání"; repeater.stop(); repeater = null; } /**Požadovaný poček krát světlem blikne nezávisla na činnosti zbytku programu. * Nula krát znamená pořád, dokud nezavoláme stopBlinking(). */ public void blink(int times, int period) { blink(times, period, () -> {}); }
Copyright © Rudolf Pecinovský, Soubor: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
27 z 54
/**Požadovaný poček krát světlem blikne nezávisla na činnosti zbytku programu, * Přičemž po ukončení blikání zavolá zadanou metodu. * Nula krát znamená pořád, dokud nezavoláme stopBlinking(). */ 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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
28 z 54
2.3.6 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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
29 z 54
2.3.7 Příklad @Test public void testBulbShape() { CM.removeAll(); lightXY = changeShape(lightXY, Rectangle::new); lightXYC = changeShape(lightXYC, Triangle::new); lightXYS = changeShape(lightXYS, () -> new Triangle(0, 0, 1, 1, Direction8.SOUTH_WEST)); CM.add(lightXY, lightXYC, lightXYS, lightXYSC); }
Copyright © Rudolf Pecinovský, Soubor: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
30 z 54
2.3.8 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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
31 z 54
2.3.9 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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
32 z 54
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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
33 z 54
2.3.10 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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
34 z 54
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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
35 z 54
/** 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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
36 z 54
2.4 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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
37 z 54
2.4.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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
38 z 54
2.4.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; můžeme jej ale uložit po přetypování Object o = (Object)(x -> x+1);
Copyright © Rudolf Pecinovský, Soubor: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
39 z 54
2.4.3 Lambda-výrazy × uzávěry ►
Lambda-výraz má tři komponenty: ● Parametry ● Blok kódu ● 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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
40 z 54
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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
41 z 54
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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
42 z 54
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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
43 z 54
3.1.2 Složitější diamantový problém
Copyright © Rudolf Pecinovský, Soubor: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
44 z 54
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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
45 z 54
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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
46 z 54
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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
47 z 54
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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
48 z 54
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 super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } }
}
default Spliterator spliterator() { return Spliterators.spliteratorUnknownSize(iterator(), 0); }
Copyright © Rudolf Pecinovský, Soubor: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
49 z 54
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 super R, ? extends V> after) { Objects.requireNonNull(after); return (T t, U u) -> after.apply(apply(t, u)); }
Copyright © Rudolf Pecinovský, Soubor: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
50 z 54
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 super T> 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 super T> comparator) { Objects.requireNonNull(comparator); return (a, b) -> comparator.compare(a, b) >= 0 ? a : b; }
Copyright © Rudolf Pecinovský, Soubor: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
51 z 54
@FunctionalInterface public interface Function { R apply(T t); default Function compose(Function super V, ? extends T> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); } default Function andThen(Function super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); }
}
static Function identity() { return t -> t; }
Copyright © Rudolf Pecinovský, Soubor: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
52 z 54
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: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
53 z 54
4. KONEC
Děkuji za pozornost
Rudolf Pecinovský [email protected] Copyright © Rudolf Pecinovský, Soubor: 09_Lambda_vyrazy.doc, verze 1.00.2413, uloženo po 10.11.2014 – 16:32
54 z 54