740 likes | 881 Views
CZJUG – 21. února 2007. Generické typy v JavÄ› Ing. Tomáš Záluský http:// www.zalusky.eu ET NETERA, a.s. http://www.etnetera.cz. Agenda. Úvod Generické typy (tÅ™Ãdy, terminologie) Generické typy (metody, wildcards) Aplikace v JavÄ› Praktické situace pÅ™i použÃvánà generik Diskuse/dotazy.
E N D
CZJUG – 21. února 2007 Generické typy v Javě Ing. Tomáš Záluský http://www.zalusky.eu ET NETERA, a.s. http://www.etnetera.cz
Agenda • Úvod • Generické typy (třídy, terminologie) • Generické typy (metody, wildcards) • Aplikace v Javě • Praktické situace při používání generik • Diskuse/dotazy
Když se řekne generický typ • „přestaňte mi nadávat, nerozumím vám“ • kdo nikdy o g. neslyšel v žádném jazyce • „jo, to jsou ty zobáky, abys nemusel furt přetypovávat ty prvky Listu“ • základní kolekce, začátky • „to, co je v Javě, je dobrý, ale na svý věci to moc nevyužívám, a ty otazníky fuj to je magie“ • aktivní používání cizího kódu, občas vlastní • „už jsem si někdy udělal třídy, kde jsem využil meze, wildcards nebo triky s inferencí a je to užitečný, i když mě to občas vypeklo“ • návrh několika vlastních (spolupracujících) g. tříd
Úvod • o přednášce • dotazník, různé názory na generiky • očekávání z přednášky • teorie/praxe, úroveň pokročilosti • přednáška pro kodéry, ne pro architekty • snaha o výklad formou „uzavřený systém“, i za cenu nepřesností a zjednodušení • co od přednášky neočekávat • spekulace nad budoucím vývojem • Bolí vás zuby? Generické třídy Vám pomůžou!
Motivace • podobnost algoritmů a datových struktur • příklad: seznam řetězců x seznam čísel class SeznamRetezcu { private String array[]; ... public String get(int index) {returnarray[index];} publicvoid set(int index, String value) {array[index] = value;} public String toString() ... public String max(Comparator comp) { String temp = array[0]; for (String current : array) { if (comp.compareTo(current,temp) > 0) {temp = current;} } return temp; } } class SeznamCisel { private Integer array[]; ... public Integer get(int index) {returnarray[index];} publicvoid set(int index, Integer value) {array[index] = value;} public String toString() ... ... }
Motivace – nevýhody ukázky… • vnější (mezi různými třídami) • opakující se copy & paste kód • není (jazykově) vyjádřena souvislost mezi třídami • jak by vypadal předek AbstractSeznam? moc by nepomohl • obtížné rozlišení typu za běhu • reflection, Class.forName("Seznam" + chciInt? "Cisel" : "Retezcu")
…Motivace – nevýhody ukázky • vnitřní (v rámci jedné třídy) • není vyjádřena souvislost mezi členy třídy • např. shoda typu vkládané a vybírané hodnoty • není vyjádřena souvislost mezi parametry metody navzájem, nebo mezi parametry a návratovou hodnotou • např. u max() by mělo být zaručeno, že Comparator porovnává objekty stejného typu, jaké vrací max() • kód je pouze demonstrativní • znalost nevýhod může být důležitá z hlediska rozhodování se, zda pro řešení daného problému použít generiky
Možnosti nápravy… • náhrada za co nejuniverzálnější typ • tj. Object • Java do příchodu 5.0 • odstraní pouze část nevýhod • opakující se kód • vyjádření souvislosti mezi třídami – zadarmo (založením předka) • rozlišení typu zůstává za běhu za pomoci instanceof • nevýhody na lokální úrovni zůstávají • zvýšená potřeba přetypovávání
…Možnosti nápravy… • generický/parametrizovaný Seznam: (parametrizovaný typem) class Seznam<T> { privateTarray[]; ... publicT get(int index) {returnarray[index];} publicvoid set(int index, T value) {array[index] = value;} public String toString() ... publicT max(Comparator<T> comp) { // spravne <? super T>, ale tady T temp = array[0];// to staci for (T current : array) { if (comp.compareTo(current,temp) > 0) { temp = current; } } return temp; } } Seznam<String> seznamRetezcu = new Seznam<String>();
…Možnosti nápravy • odstranění všech výše uvedených nevýhod • opakující se kód • zachování souvislostí mezi třídami • rozlišení typu přechází do compile-time • zachování typových souvislostí mezi členy třídy i v rámci kontraktu metod
Historie a souvislosti • C++ template • JSR-14 (http://jcp.org/en/jsr/detail?id=14) • Java 5.0 „Tiger“
Generické typy I. OOP Generické třídy Raw typ, erasure, meze Vztah mezi parametrickými typy
Jemné připomenutí OOP… • rozdíl • statický (=deklarovaný) x dynamický (=runtime) typ • List list = new ArrayList(); • „potomek“ a „předek“ • kompatibilita vzhledem k přiřazení • dědičnost x implementace interfacu – není tak důležité • objektová hierarchie • kořen Object, větvení stromu „oběma směry“ class Parent {...} class Child extends Parent {...} Parent parent = new Parent(); // lze Child child = new Child(); // lze Parent parent = child;// lze, ale nutny cast ((Child)parent).childMethod(); Child child = parent; // nelze, za parentem muze byt v runtime // jiny potomek
Úvod do generických typů • T = typový parametr • typový argument • konkrétní typ dosazený za typový parametr • parametrizovaný typ • generický typ po dosazení typového argumentu za typový parametr • analogie s voláním metod • deklarace: void foo(int p) {...} // p je parametr • volání: foo(2); // 2 je argument • různé terminologie • parametrizovaný typ x instance parametrizovaného typu • pozor: neplést s instancí ve smyslu nového objektu vytvořeného pomocí new!
Odlišnost koncepcí v C++ a Javě • C++ přístup • každá instance se přeloží do zvláštní třídy, jakoby se dosadil argument na úrovni zdrojového souboru (makro) • vytvoří se tolik „syntetických“ tříd, kolik je instancí generického typu • typová informace je přístupná za běhu • Java přístup • při překladu se provede kontrola vztahů mezi objekty z hlediska typové kompatibility (přiřazení, cast) • poté se typová informace odstraní (není tedy přístupná za běhu) a zůstane jedna třída • tím je zaručena runtime typová bezpečnost
Generické třídy • zápis class Nazev <TypovyParametr1,...> { // telo tridy } • code convention • doporučení: 1písmenný typový parametr • klíč mapy K, hodnota mapy/kolekce V (T,E), výjimka E (X), obecný typ T (U,…) • typové parametry v <> • jsou součástí názvu generické třídy • mohou se používat zhruba kdekoli, kde se očekává třída, tj. přesně:
Použití generických tříd – kde ano • tvorba nové instance: ... = new ArrayList<T>() • použití na levé straně přiřazení: List<T> list = ... • přiřazení pole je sice možné, ale nepřináší výhody: List<T>[] arrayOfLists = ... • deklarace lokální a instanční proměnné: private List<T> list = ... • vyhození výjimky generického typu: public <E extends Exception> void method()throws E{...} • potomek generického předka: public class MySmartList<T> implements List<T>...
Použití generických tříd – kde ne… • generické výjimky • generický typ nesmí být odvozen od Throwable class MyException<T> extends Exception ... • generický enum: enum Foo<T> {...} • přímá tvorba objektu nebo pole operátorem new: new T(); new T[] {...} • instanceof: if (variable instanceof List<String>) ... • class literal: Class<List<String>> clazz = List<String>.class; • přímo jako supertyp: class Foo<T> extends T;
…Použití generických tříd – kde ne • tvorba pole prvků generického typu new ArrayList<T>[] • import import java.util.List<String>; • statický kontext • proměnné, metody, vnitřní statické třídy, interfacy, enums: 01publicclass Foo <T,U> { 02staticclass FooStaticClass { 03void hoo(T arg) {} 04} 05class FooClass { 06void hoo(T arg) {}P U Z Z L E: 07} 08interface FooInterface {Na kterých řádcích ohlásí 09void hoo(T arg);překladač chybu? 10} 11interface Hoo<T> { 12void hoo(T arg, 13U arg2); 14}}
…Použití generických tříd – kde ne • tvorba pole prvků generického typu new ArrayList<T>[] • import import java.util.List<String>; • statický kontext • proměnné, metody, vnitřní statické třídy, interfacy, enums: 01 publicclass Foo <T,U> { 02staticclass FooStaticClass { 03void hoo(T arg) {}// NELZE - static context 04} 05class FooClass { 06void hoo(T arg) {} // OK 07} 08interface FooInterface { 09void hoo(T arg);// NELZE - static context 10} 11interface Hoo<T> { 12void hoo(T arg,// T je zcela nový typ, OK 13U arg2);// U je zakázaný 14}}
Generické třídy • typový parametr – pouze referenční typ, tj.: • ano: • přímo typ, tj. vše, co extends Object: new ArrayList<String>() • pole: new ArrayList<String[]>() • generický typ: new ArrayList<ThreadLocal<String>>() • wildcard typ: new ArrayList<ThreadLocal<?>>() • ne: • primitivní typ a void • wildcard: new ArrayList<?>()
Generické třídy • parametrizovaný typ: List<String> listOfStrings = new ArrayList<String>(); • typová kontrola: • překladač ohlídá správný typ: listOfStrings.add(2005); // chyba • snížení castů: String s = listOfStrings.get(1);
Terminologie • raw type • raw = základní, surový, čistý • typ po odstranění typových parametrů • raw typ ke generickému typu List<String> je List • type erasure • erasure = vymazání, očištění • proces odstranění typového parametru z generického typu • provádí jej překladač po kontrole typové kompatibility
Důsledky • za běhu se pracuje pouze s raw typem • Příklad: listOfStrings.getClass().getName() • vrací "java.util.List" • nikoli "java.util.List<String>" • snaha vyhnout se raw typům v deklaracích • může komplikovat závislost na kódu třetí strany • type erasure = společný důvod všech „kde ne“ • např. statický kontext – společný všem instancím generické třídy • new T – překladač by nevěděl, co má nechat vytvořit, protože vidí jen Object • typová informace se nedostane do .class souboru
Shrnutí • generiky (téměř) nejsou záležitostí runtime !!!
Meze generických parametrů (bounds) • specifikují úžeji třídu nebo rozsah tříd typových argumentů • zápis: • T extends HorniMezT může být HorniMez nebo její potomek (přetížení klíčového slova extends) • T extends HorniMez & Interface1 & Interface2T musí splňovat výše uvedenou podmínku pro všechny typy oddělené znakem & • zúžení typu parametrů může být • absolutní: class Trida <T extends Number> {...} • vzájemné: class Trida <S, T extends S> {...} • není-li mez uvedena, chápe se jako Object. • type erasure je náhrada typu mezí
Vztah mezi instancemi generického typu • List<Integer> • může obsahovat pouze instance třídy Integer • List<Number> • může obsahovat Integer, Long, Short,... • homogenita a heterogenita jsou relativní pojmy • homogenní = nemohou v něm být Stringy • heterogenní = mohou v něm být různí potomci Number • mezi instancemi generického typu neexistuje dědění ani kompatibilita vzhledem k přiřazení • a to ani tehdy, pokud to platí pro typové argumenty
Instance generického typu – příklad • důvod: • pokud by to bylo přípustné, šlo by do objektu listOfNumbers uložit Long přes proměnnou listOfNumbers a pak ji chtít vybrat přes listOfInteger => ClassCastException • rozdíl:statický (deklarovaný) x dynamický (runtime) typ • stejné jako před generikami List<Integer> listOfIntegers; List<Number> listOfNumbers; listOfIntegers = new ArrayList<Number>();// chyba listOfNumbers = new ArrayList<Integer>();// chyba listOfIntegers = listOfNumbers;// chyba listOfNumbers = listOfIntegers;// chyba listOfIntegers = (List<Integer>)listOfNumbers; // chyba listOfNumbers = (List<Number>)listOfIntegers;// chyba
Vztah generického typu a raw typu • přiřazení i cast jsou možné, ale vedou na – • unchecked warning • upozornění překladače, že nemá dost informací, aby zajistil typovou bezpečnost • uživatel se dívá na objekt pohledem, který mu umožňuje s ním neoprávněně pracovat • typicky: vložit do kolekce „vetřelce“ • v Eclipse hláška „Type safety“ • při přechodu z 1.4 nevyhnutelné • z dlouhodobého hlediska nežádoucía měly by se eliminovat
Potlačení unchecked warningu • anotace @SuppressWarnings("unchecked") • použití • pokud překladač hlásí unchecked warning, ale ze situace plyne, že práce s typy je bezpečná • v případě specifické kombinace lenosti, nechutě opravovat warningy a časové tísně • anotovat lze třída nebo metoda • potlačí všechny warningy v metodě ()
Odůvodněný @SuppressWarnings • 2 ukázky: klonování + obejití statického kontextu publicstaticclass Wrapper<T> implements Cloneable { @SuppressWarnings("unchecked") public Wrapper<T> safeClone() throws CloneNotSupportedException { return (Wrapper<T>)this.clone(); } } publicstaticclass EmptyIterator <T> implements Iterator<T> { privatestatic EmptyIterator instance = new EmptyIterator(); private EmptyIterator() {} @SuppressWarnings("unchecked") publicstatic <U> EmptyIterator<U> getInstance() { return (EmptyIterator<U>)instance; } publicboolean hasNext() { returnfalse; } ... }
Generické typy II. Generické metody Wildcards
Generické metody • metody mohou být parametrizované podobně jako třídy • zápis: • deklarace typových parametrů se zapisuje před návratovou hodnotu • příklady:public <T> int foo(T object) {...}public <T> T max(Collection<T>) {...} • parametrizovaná metoda(instance generické metody) • analogie k parametrizované třídě • tj. metoda po dosazení typových argumentů za typové parametry
Genericita třídy a metody • jsou nezávislé rysy • generická třída i generická metoda • příklad:java.util.Collection<E>public <T> T[] toArray(T[]) • parametry spolu nesouvisejí, případně se zastiňují • negenerická třída, generická metoda • příklad:java.util.Collectionsstatic <T> List<T> singletonList(T o)většina metod • ostatní kombinace
Inference… • type argument inference= odvození typového argumentu • proces nalezení instance generické metody • na základě typu jednotlivých částí kontraktu metody • odehrává se za překladu publicclass GenericMethod { publicstatic <Textends Number> void gm(T par) { System.out.println("Number"); } publicstatic <Textends Integer> void gm(T par) { System.out.println("Integer"); } publicstatic <Textends String> void gm(T par) { System.out.println("String"); } publicstaticvoid main(String[] args) { GenericMethod.gm(1);// Integer GenericMethod.gm("abc");// String GenericMethod.<Number>gm(1); // Number Number number = new Integer(1); GenericMethod.gm(number);// Number }}
…Inference… • mechanismus inference • opírá se jak o typ předaných parametrů, tak o typ návratové hodnoty • může se lišit v Eclipse compileru a javacu • může vypéct
…Inference • explicitní specifikace typu argumentu • sdělíme sami překladači, jaké argumenty dosadit • u generických tříd vždy • návrhy do JDK7 na zjednodušeníList<String> list = new ArrayList(); • u generických metod potřeba, když automatický mechanismus neodpovídá záměrům programátora • důvody použití: • negativní: lenost, nechce se zabývat inferencí • pozitivní: snaha o defenzivnost(zajištění přeložitelnosti na více překladačích) • u instančních metod nutno použít this this.<Number>gm(1);
Wildcards… • co je wildcard • označení pro určitou přesně vymezenou množinu parametrizovaných typů • prostředek pro zastřešení nekompatibilních parametrických typů do hierarchie • příklady • List<? extends Number> • Comparable<? super Integer> • Collection<?> • Map<String,? extends List<T>>
…Wildcards… • zápis • ?–neomezený wildcard • unbounded • čteme: „cokoli“ nebo „neznámý typ“ • ? extends HorniMez – shora omezený • upper bound • čteme: „cokoli co je HorniMez nebo potomek“ • ? super DolniMez – zdola omezený • lower bound • čteme: „cokoli co je DolniMez nebo předek“ • meze mají syntaxi odlišnou od mezí g. typů • žádné vícenásobné meze (&), ale zase super • další přetížení klíčového slova extends
…Wildcards… • wildcard typy • nejsou konkrétní generické typy jako List<String>, Comparable<Number>,… • ale je nutno je za nimi vidět • wildcard typ se také nazývá rodina konkrétních instancí generického typu • zápis List<? extends Exception> … • ...je potřeba číst jako: „homogenní seznam objektů Exception nebo objektů třídy, která je potomkem Exception“ • ...nelze číst jako „seznam, ve kterém může být cokoli, co je potomkem Exception“ • nesprávnému chápání by odpovídal typ List<Exception>, který je pouze podmnožinou wildcard typu • homogenita x heterogenita • rozdíl Collection<?> x Collection<Object> – viz diagram
…Wildcards… • mezi wildcard typy existuje vztah velmi podobný dědění • můžeme používat pojmy „předek“ a „potomek“ • wildcard typ T1 je předkem wildcard typu T2, jestliže odpovídající množina konkrétních parametrizovaných typů pro T1 je nadmnožinou množiny konkrétních parametrizovaných typů pro T2 • lidsky řečeno: když jsou bubliny na obrázku v sobě • kompatibilita vzhledem k přiřazení • jak zjistit, zda 2 typy jsou ve vztahu předek-potomek publicclass Inheritance { publicstatic <T,UextendsT> void test() {} publicstaticvoid main(String[] args) { Inheritance.<Object,String>test(); Inheritance.<Collection<? extends Number>,Collection<? extends Integer>>test(); } }
…Wildcards • wildcards je užitečné znát, protože • poskytují vazbu mezi jednotlivými instancemi generického typu • současně tvoří hierarchii, analogie s hierarchií objektů • root je Object x Rawtype<?> • wildcard typy x abstraktní třídy • hierarchie není strom, ale acyklický graf
Wildcards – pokročilejší • capture („otisk“, ne „záchytka“ ) • fiktivní konkrétní typ, který představuje wildcard typ • je to syntetický typ, se kterým operuje interně překladač při kontrole zajištění typové bezpečnosti • objevuje se v chybových hlášeních • omezení volání metod na objektu wildcard typu • List<?> list = ...list.add(1); // chyba • důvod: neznalost konkrétního parametrizovaného typu • zakázanost metody je dána výskytem T v argumentech metody • metoda vracející T je povolena, přístup přes Object • u omezených wildcards složitější • víceúrovňové wildcard
Aplikacev Javě Collections Class Ostatní
Collections… • java.util.Iterable<E> a potomci • java.util.Collection<E> • void addAll(Collection<? extends E>) • aby bylo možné přidat i kolekci potomků, taktéž u Map.putAll() • Iterator<E> iterator() • <T> T[] toArray(T[]) // <T super E> • převede na pole, jehož runtime typ je určen runtime typem zadaného pole • java.util.Iterator<E> • java.util.Map<K,V> • java.util.Map.Entry<K,V> • Set<Map.Entry<K,V>> entrySet() • Set<K> keySet() • void putAll(Map<? extends K,? extends V>) • Collection<V> values()
…Collections… • java.util.Collections<E> (statické metody) • <T> boolean addAll(Collection<? super T>, T...) • umožní zadat potomka do kolekce předků, nelze totiž<T> boolean addAll(Collection<T>, ? extends T...) • <T> int binarySearch(List<? extends Comparable<? super T>>, T) • umožní vyhledat potomka v seznamu předků v případě, že Comparable je definováno na předkovi • <E> Collection<E> checkedCollection(Collection<E>, Class<E>) • kolekce s kontrolou typové bezpečnosti za běhu • brání zavlečení nepořádku v podobě směsi generických a raw typů • myšlenka:když už se nepodaří přesunout kontrolu z runtime do compile-time, měla by se přesunout alespoň co nejblíže místu vzniku chyby
…Collections • java.util.Collections<E>, pokračování • <T> void copy(List<? super T>, List<? extends T>) • <T> List<T> emptyList() • <T extends Object &Comparable<? super T>>T max(Collection<? extends T>) • Comparable<? super T> je ze stejného důvodu jako u binarySearch • mez Object je z důvodu binární kompatibility • bez Object by po erasure zbyloComparable max(),což by neodpovídalo původnímuObject max() • void reverse(List<?>) • implementace používá raw typy • možná implementace použitím pomocné generické metody<T> void reverseHelper(List<T>)
Class… • java.lang.Class<T> • kompenzuje absenci runtime informace – pokud runtime informaci potřebujeme, předáme Class<T> • parametrizovaná typem, který třída představuje, např. třída řetězce "abc" je Class<String> • Class<? super T> getSuperclass() • T newInstance() • boolean isInstance(Object) • runtime ekvivalent instanceof • T cast(Object) • umožňuje přetypovat objekt na třídu, kterou známe v runtime, tak aby v compile-time zůstala zachována schopnost zaručit typovou bezpečnost publicstatic <T> void filter(Class<T> clazz, List<?> src, List<T> dest) { for (Object o : src) { if (clazz.isInstance(o)) dest.add(clazz.cast(o)); } }