Vaše jistota na trhu IT Generické a parametrizované datové typy a metody Rudolf PECINOVSKÝ 1
Vaše jistota na trhu IT Úvod ►Shrnutí či téma 2
3 Doporučená literatura 1/2 ►Java 5.0 – Novinky jazyka a upgrade aplikaci ►Vydala Computer Press, ISBN , EAN: ►Probírá vlastnosti, které stále mnozí neznají – především parametrizované typy a anotace ►Lze volně stáhnout na adrese java5novinky/ java5novinky/
4 Doporučená literatura2/2 ►CZJUG – Tomáš Záluský ● ►SUN: ● ● ►SUN Generics forum: ● ►Gilad Bracha ● ►Angelika Langer Generic FAQ: ● ►Brian Goetz ● ●
5 Terminologie a zkratky ►Používané zkratky: ● GT/ PT– generický/ parametrizovaný typ (typy) ● GM/ PM– generický/ parametrizovaná metoda (metody) ● GTM/ PTM– generické/ parametrizované typy a metody ● TP– typový parametr ►Generický = obecný, obecně použitelný, rodový ● Parametrizovaná metoda != metoda s parametry ►GT/GM mají typové parametry zastupující některé z typů použitých v definovaném typu, resp. metodě ►Seznam typových parametrů se uvádí ve špičatých závorkách ►Parametrizovaný typ je potomek generického typu s dosazenou hodnotou typového parametru ● G: class Typ1G {} interface Typ2G {} ● P : class Typ1P extends Typ1G {} interface Typ2P extends Typ2G {}
6 Řešený problém ►Při definici kontejnerů nebylo možno specifikovat typ hodnot ukládaných do kontejneru – typovou kontrolu bylo možno provést až za běhu ►Některé jazyky (např. Ada, C++) řeší tento problém zavedením typových parametrů, jejichž hodnoty je možno kontrolovat již při překladu ►Cíle při implementaci v Javě (2004) ● Maximální kompatibilita s předchozími verzemi Javy ● C++ vyrábí pro každou hodnotu parametru novou třídu – tudy se tvůrci Javy dát nemohli ● Jeden typ musí pracovat stejně dobře pro všechny hodnoty parametru ►Cíle při implementaci v.NET 2.0 (2005) ● Spojit maximum výhod C++ a Javy
Cíle generického programování ►Vyjádřit algoritmy s minimální závislostí na použitých datových strukturách ►Vyjádřit datové struktury s minimální závislostí na algoritmech ►Implementovat algoritmy co nejobecněji, aniž by při přechodu ke konkrétním typům došlo ke ztrátě efektivity ►Nevyhovuje-li obecná datová struktura pro speciální případy, poskytnout programátorovi možnost implementovat tyto speciální případy zvlášť ►Existuje-li k řešení určitého problému několik rovnocenných algoritmů, poskytnout programátorovi možnost implementovat je všechny a dát tak uživateli na vybranou podle dalších kritérií 7
Řešení v Javě ►Typové parametry generických typů slouží jako informace o typech objektů pouze pro překladač ● Rozhoduje o přípustnosti použitého typu ● V případě potřeby vloží potřebné přetypování ►Přeložený kód je od těchto informací „očištěn“ a žádné dodatečné typové informace již neobsahuje ►Programy je třeba psát tak, aby nebylo třeba za běhu rozhodovat podle hodnot typových parametrů ►Operace vyžadující znalost hodnot typových parametrů za běhu jsou zakázány – způsobí syntaktickou chybu ►Parametrizované typy si pamatují hodnoty typových parametrů svých rodičů 8
Řešení v.NET ►Řešení se liší podle specifiky datového typu ● „Hodnotové“ datové typy včetně primitivních mají pro každý použitý typ definovaný speciální verzi (cesta C++) ●Lze to, protože jich je „konečně mnoho“ ● Odkazové datové typy jsou řešeny podobně jako v Javě ►Výhody oproti Javě: ● Generické konstrukce lze používat i pro parametry primitivních typů (Java se omezuje pouze na objektové typy) ● Instance si s sebou nesou informace o hodnotách typových parametrů ►Nevýhody oproti Javě ● Bylo třeba zcela přebudovat virtuální stroj i jazyk IL (.NET byl ale v té době ještě „mládě“, tak to tak moc nevadilo) ● Nedají se použít žolíky ● U „hodnotových“ typů jsou přeložené třídy závislé na třídách, jež je používají, protože potřebují mít vytvořeny příslušné verze 9
Vaše jistota na trhu IT Jednoduché použití 10
PT + TP – možná použití ►Deklarace atributu či lokální proměnné ● class Třída { T atribut; } ● Map mapa; ● List seznam = new ArrayList<>(); ►Typ parametru či návratové hodnoty ● T max( Collection col ) { /*…*/ } ►Typ vyhozené výjimky ● public void metoda() throws E { /*…*/ } ►Potomek generického předka ● class ChytrýSeznam extends ArrayList { /*…*/ } 11
Poznámky ►Seznam typových parametrů je součástí názvu třídy ►Parametr je možné použít všude, kde bychom použili příslušnou třídu ►Analogie s metodami: formální a skutečné parametry ►Typ prvků ve frontě bude určen až při vytváření konkrétní instance fronty ►Různé instance mohu ukládat objekty různých typů ►Hodnoty typových parametrů jsou známy pouze v době překladu => typové parametry nelze použít v situacích, kdy je třeba znát jejich hodnoty za běhu 12
VŠE – 05 Copyright © 2006, Rudolf Pecinovský 13 Definice vlastního generického typu public class FrontaP { List prvky = new ArrayList (); public FrontaP() {} public void zařaď( E prvek ) { prvky.add( prvek ); } public boolean isPrázdná() { return (prvky.size() == 0); } public E další() { E ret = prvky.get(0); prvky.remove(0); return ret; } public String toString() { return prvky.toString(); } ►Třída FrontaP pracuje s objekty typu E ● FrontaP = Fronta s typovým Parametrem ►Pomocí typového parametru definujeme skutečný typ objektů ve frontě ● FrontaP ►Hodnota TP specifikuje typ parametru či typ návratové hodnoty
Dědičnost parametrizovaných typů1/2 14 public class FrontaPI extends FrontaP implements Iterable { public Iterator iterator() { return new Iterator () { int pořadí = 0; public boolean hasNext() { return (pořadí < prvky.size()); } public E next() { return prvky.get( pořadí++ ); } public void remove() { prvky.remove( 0 ); } }; } ► FrontaP = fronta s (typovým) parametrem FrontaP ► FrontaPI = FrontaP iterovatelná
Dědičnost parametrizovaných typů2/2 ►Můžeme definovat potomka, který bude i nadále generický (příklad na minulé stránce), anebo parametrizovaného potomka (příklad na této stránce), tj. potomka s konkrétní hodnotou typového parametru – parametrizovaného typu ►V uvedené třídě není třeba definovat nic nového – hodnota typového parametru je pouze informaci pro překladač ► FrontaPIS = FrontaPI „stringů“ 15 public class FrontaPIS extends FrontaPI { //Nepotřebuje další upřesnění, vše říká TP (typový parametr) }
Metody s typovými parametry ►Jako generické můžeme definovat nejenom datové typy, ale i jednotlivé metody ►Typové parametry se uvádějí před typem návratové hodnoty ►Při volání takovéto metody si skutečnou hodnotu TP překladač často domyslí; pokud ne, musíme ji uvést ►Skutečnou hodnotu TP píšeme za kvalifikaci => potřebujeme-li uvést TP, musíme metodu kvalifikovat, i když by to jinak nebylo potřeba 16
Příklad generické metody public class Metody { public static List dvojice( T prvek ) { List ret = new ArrayList (); ret.add( prvek ); return ret; } public void volání() { Metody m = new Metody(); List ls = dvojice( "Text" ); List lo; // lo = dvojice( "Text" ); //Nesouhlasí typy // lo = dvojice("objekt"); //Nekvalifikované lo = Metody. dvojice("Kvalifikace třídou"); lo = m. dvojice( "Kvalifikace instancí"); lo = dvojice( (Object)"Řešení přetypováním" ); } 17
Vaše jistota na trhu IT Omezení hodnot typových parametrů 18
VŠE – 05 Copyright © 2006, Rudolf Pecinovský 19 Motivace ►Občas potřebujeme, aby typové parametry vyhovovaly daným omezením – např. aby implementovaly nějaké rozhraní public class IntervalU > { private final T dolní, horní; public IntervalU( T dolní, T horní ) { if( dolní.compareTo( horní ) > 0 ) throw new IllegalArgumentException( "Dolní mez nemůže být větší než horní" ); this.dolní = dolní; this.horní = horní; } public T getDolní() { return dolní; } public T getHorní() { return horní; } public boolean uvnitř( T t ) { return (dolní.compareTo( t ) <= 0) && (t.compareTo( horní ) <= 0); } I pro implementaci rozhraní se používá extends
Syntaxe1/2 ►TP je potomkem definovaného typu nebo implementuje definované rozhraní (horní omezení) T extends Rodič ►Klíčové slovo extends se uvádí jak pro dědičnost (rodič je třída), tak pro implementaci (rodič je interface ) ►Musí-li být TP „potomkem“ několika jiných typů oddělujeme tyto typy znakem &: T extends Typ1 & Typ2 & Typ3 ● Čárka je nepoužitelná, protože odděluje jednotlivé parametry, např. Generický ►Má-li být potomkem třídy a současně implementovat nějaké rozhraní, musí být jeho rodičovská třída uvedena jako první: T extends Rodič & Rozhraní1 & Rozhraní2 20
Syntaxe2/2 ►Dříve uvedený parametr se v definici může vyskytnout znovu: TestSeznamů > Posluchač > ►Nejsou povoleny dopředné reference TestSeznamů, E> ►Můžeme také požadovat, aby typový parametr byl předkem definovaného typu (dolní omezení): T super Potomek ►Dolní omezení lze klást pouze na žolíky (viz dále) ►Není možno současně požadovat extends i super 21
Vaše jistota na trhu IT Očišťování a jeho problémy 22
Terminologie ►Ozdobený datový typ (decorated type) – typ doplněný o typové parametry (skutečné hodnoty) ►Očištěný (surový, neozdobený) typ (row type) – typ použitý v přeloženém programu, zbavený případných informací nesených typovými parametry ►Při překladu se datové typy očišťují (erasure), takže v přeloženém programu není možno informace o hodnotách typových parametrů použít ►Při očišťování překladač vloží potřebná přetypování 23
Princip očištění ►Je-li na TP kladeno nějaké horní omezení, bude za něj v přeloženém kódu substituován požadovaný rodič ►Není-li na TP kladeno žádné horní omezení, bude za něj substituován typ Object ►Má-li TP několik horních omezení, bude pro substituci použito první z nich ►Neuvědomíme-li si všechny důsledky očistění, můžeme do programu zanést nechtěné chyby 24
Vliv pořadí uvedeny TP na kód 25 class Čištění_Před & Serializable, SC extends Serializable & Comparable > { CS cs; SC sc; void upravCS( CS p ) { if(cs.compareTo(p) < 0) cs = p; } void upravSC( SC p ) { if(sc.compareTo(p) < 0) sc = p; } } public class Čištění_Po { Comparable cs; Serializable sc; void upravCS(Comparable p) { if(cs.compareTo((Object)p) < 0) cs = p; } void upravSC(Serializable p) { if(((Comparable)sc).compareTo((Object)p) < 0) sc = p; }
Nejednoznačnosti a kolize ►Falešně přetížená metoda ● Metoda, která má po očištění stejnou signaturu, jako metoda již existující ►Nová metoda koliduje se zděděnou ● Metoda, která se tváří, že nepřekrývá zděděnou metodu, ji po očištění začne překrývat ►Kolize požadovaných rozhraní ● Dceřiná třída dědí rozhraní implementované svým rodičem včetně hodnoty případného typového parametru, kterou je často typ rodiče, ale potomek by potřeboval sebe ►Špatné pochopení dědičnosti typů ● Rozebráno v dalších kapitolách 26
Příklad: Falešně přetížená metoda public class Přepravka12 { T1 první; T2 druhý; public T1 get1() { return první; } public T2 get2() { return druhý ; } public void set( T1 o ) { první = o; } public void set( T2 o ) { druhý = o; } } 27 ►Obě metody set mají ve skutečnosti stejné typy parametrů
Příklad: Nová metoda koliduje se zděděnou public class Přepravka11 { T první; T druhý; public T get1() { return první; } public T get2() { return první; } public boolean equals( T t ) { return (první.equals(t) && druhý.equals(t)); } 28 ►Metoda equals je po očištění definována jako equals(Object), ale přitom neakceptuje libovolný objekt, a proto nepřekrývá korektně zděděnou verzi
Příklad: Kolize požadovaných rozhraní class Rodič implements Comparable { public int compareTo(Rodič r) { return 0; } } //=============================================================== class Potomek extends Rodič {} //=============================================================== class Kolize_NesedíRozhraní { //Toto ještě projde – potomek se vydává za rodiče IntervalU iur = new IntervalU ( new Rodič(), new Potomek() ); //Implementuje špatné rozhraní – //zdědil implementaci Comparable, //avšak konstruktor požaduje implementaci Comparable IntervalU iup = new IntervalU (new Potomek(), new Potomek()); } 29 IntervalU >
Příklad: Kolize implementovaných rozhraní class Rodič implements Comparable { public int compareTo( Rodič r ) { return 0; } } //================================================================== class Potomek extends Rodič implements Comparable {} 30 ►Kód vyvolá při překladu chybu java.lang.Comparable cannot be inherited with different arguments ►Příčina: nelze implementovat stejné rozhraní jako rodič, ale s odlišnými hodnotami typových parametrů
Příklad: Špatné pochopení dědičnosti ►Program: /*1*/ List str = new ArrayList (); /*2*/ List obj = str; //Chyba!!! /*3*/ obj.add(new Object()); /*4*/ String s = str.get(0); špatně interpretuje dědičnost generických typů ►Dědičnost typových parametrů neimplikuje dědičnost typů s těmito parametry ● Je to jinak než u polí – tam je pole potomků potomkem pole předků 31
Dědičnost parametrizovaných typů 32 List «surový» ArrayList «surový» List ArrayList List
Vaše jistota na trhu IT Nepovolená použití 33
34 new TP ►Nelze vytvořit instanci typového parametru pomocí operátoru new ● atribut = new T(); ● K vytvoření instance potřebuji konstruktor. ● Nevím, jaké konstruktory bude daný typ poskytovat ● Instance lze vytvářet jen klonováním či továrními metodami ►Lze ale vytvořit instancí parametrizovaného typu ● List seznam = new ArraList () ● Konstruktor parametrizovaného typu je známý, takže překladač přesně ví, kterou metodu zavolat
35 new PT[]; primitivní TP ►Nelze vytvořit pole instancí typového parametru ani parametrizovaného typu ● pole1 = new T[](); pole2 = new ArraList (); ● Pole si musí umět pamatovat deklarovaný typ svých prvků Object[] oo = new String[5]; oo[0] = 123; //Vyvolá běhovou chybu ● Neznám-li skutečný typ prvků, nemohu pole vytvořit ►Hodnotami typových parametrů nesmí být primitivní typy ● List seznamCelýchČísel; ● Koncepce PT je v Javě založena na dědičnosti – do ní primitivní datové typy nezapadají
36 Statické členy; Throwable ►Typové parametry třídy není možno použít u jejích statických členů ● classs Třída { static T atribut; static T metoda( T param ); } ● Statické členy jsou sdíleny instancemi dané třídy ● Každá z instancí může mít přiřazenu jinou hodnotu typového parametru => neví se, kterou z nich aplikovat ● V případě potřeby musejí mít statické metody vlastní typový parametr ►GT nesmí být potomkem Throwable ● class Výjimka extends Exception {/*…*/} ● Při vyhazování výjimky nevíme, kdo ji zachytí, a nemůžeme mu proto předat informace, od nichž byla instance očištěna => parametrizace výjimky nemá smysl
37 Výčtový PT; instanceof ; TP.class ►Výčtové typy ● enum Výčet {/*…*/} ● Výčtové typy mají své instance definovány přímo ve třídě kde se všechny potřebně typy nastaví rovnou => není tu místo pro budoucí volbu ● Navíc jsou příliš těsně provázané se svým rodičem, překladačem a virtuální strojem ►Nelze použít operátor instanceof ● if (variable instanceof List ) //… ►Nelze použít literál class-objektu ● Class clt = T.class; Class > cls = List.class; ● Literál je konstanta, jejíž hodnota je známa v době překladu. To ale u TP známo není ● PT zase nepřidá využitelnou informaci
38 TP jako rodič; import PT ►TP nelze použít jako rodiče ● class Foo extends T; ● Konstruktor musí volat konstruktor předka, ale toho nezná ►Nelze použít ● import java.util.List ; ● V příkazu import lze používat pouze surové, neozdobené typy
Vaše jistota na trhu IT Žolíky 39
40 Účel ►Slouží k řešení problémů způsobených omezeními dědičnosti parametrizovaných typů ►Může-li na daném místě být objekt libovolného typu, použijeme žolík ? ● Class třída = parametr.getClass(); ►Může-li být na daném místě objekt libovolného typu vyhovujícího zadanému omezení, použijeme žolík s příslušným omezením ● Pro potomka typu X: ● Pro rodiče typu Y: ● Omezení lze kombinovat: >
Příklad: Žolík jako potomek public static void test() { List lik; lik = new ArrayList (); naplň( lik ); nakresli( lik ); lik = new LinkedList (); naplň( lik ); nakresli( lik ); } public interface IMístnost extends IPojmenovaný public String getNázev(); public String getPopis(); public Collection getPředměty(); public Collection getSousedé(); } 41
Příklad: Žolík jako předek // public class IntervalUž > public class SrovnáníRozhraníŽolíkem { class Rodič implements Comparable public int compareTo( Rodič r ) { return 0; } } class Potomek extends Rodič {} // implements Comparable {} IntervalUž iur = new IntervalUž ( new Rodič(), new Potomek() ); IntervalUž iup = new IntervalUž ( new Potomek(), new Potomek() ); } 42
Vaše jistota na trhu IT Děkuji za pozornost ►Rudolf Pecinovský mail: ICQ:
44
Pgm Používaná písma a objekty ► Pgm Příliš žluťoučký kůň úpěl ďábelské ódy (Demi) ● Pgm Příliš žluťoučký kůň úpěl ďábelské ódy (Medium) ● Pgm Příliš žluťoučký kůň úpěl ďábelské ódy (Cond) ►Příliš žluťoučký kůň úpěl ďábelské ódy (Heavy) ● Příliš žluťoučký kůň úpěl ďábelské ódy (Franklin Gothic Book) ● Příliš žluťoučký kůň úpěl ďábelské ódy (Comic Sans MS) ● Příliš žluťoučký kůň úpěl ďábelské ódy (Consolas) Program Keyword Opakování Příliš žluťoučký kůň úpěl ďábelské ódy 45