1 / 73

CZJUG – 21. února 2007

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.

pepper
Download Presentation

CZJUG – 21. února 2007

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. CZJUG – 21. února 2007 Generické typy v Javě Ing. Tomáš Záluský http://www.zalusky.eu ET NETERA, a.s. http://www.etnetera.cz

  2. Agenda • Úvod • Generické typy (třídy, terminologie) • Generické typy (metody, wildcards) • Aplikace v Javě • Praktické situace při používání generik • Diskuse/dotazy

  3. 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

  4. Ú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!

  5. 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() ... ... }

  6. 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")

  7. …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

  8. 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í

  9. …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>();

  10. …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

  11. Historie a souvislosti • C++ template • JSR-14 (http://jcp.org/en/jsr/detail?id=14) • Java 5.0 „Tiger“

  12. Generické typy I. OOP Generické třídy Raw typ, erasure, meze Vztah mezi parametrickými typy

  13. 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

  14. …Jemné připomenutí OOP

  15. Ú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!

  16. 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

  17. 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ě:

  18. 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>...

  19. 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;

  20. …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}}

  21. …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}}

  22. 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<?>()

  23. 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);

  24. 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

  25. 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

  26. Shrnutí • generiky (téměř) nejsou záležitostí runtime !!!

  27. 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í

  28. 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

  29. 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

  30. 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

  31. 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ě ()

  32. 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; } ... }

  33. Generické typy II. Generické metody Wildcards

  34. 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

  35. 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

  36. 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 }}

  37. …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

  38. …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);

  39. 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>>

  40. …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

  41. …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

  42. …Wildcards…

  43. …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(); } }

  44. …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

  45. 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

  46. Aplikacev Javě Collections Class Ostatní

  47. 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()

  48. …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

  49. …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>)

  50. 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)); } }

More Related