Programování v jazyce Java Tomáš Pitner, Radek Ošlejšek, Marek Šabo Generické typy, typové parametry • Generické typy = něco obecně použitelného , zobecnění • Třídy v Javě mají společného předka, třídu Object (každý objekt je instancí třídy Object). • Potřebujeme-li pracovat s nějakými objekty, o kterých neznáme typ, můžeme využít společného předka a pracovat s ním. • To umožňuje snadnou implementaci kolekcí, ale například i využití reflexe. Vtip Deklarace seznamu bez a s generiky // no generics (obsolete) public interface List { ... } // generic type E public interface List { ... } • do špičatých závorek umístíme symbol — seznam bude obsahovat prvky E (předem neznámého) typu • je doporučováno používat velké, jednopísmenné deklarace • písmeno vystihuje použití — T je type, E je element • E nahradíme jakoukoliv třídou nebo rozhraním 1 Jednoduché využití v metodách E get1(int index); Object get2(int index); • get1 vrací pouze objekty, které jsou typu E — je vyžadován speciální typ • get2 vrací libovolný objekt, tj. musíme pak přetypovávat boolean add(E o); • přidává do seznamu prvky typu E Výhody generik List numbers1 = new ArrayList(); numbers1.add(1); numbers1.add(new Object()); // allowed, unwanted Integer n = (Integer) numbers1.get(0); List numbers2 = new ArrayList<>(); numbers2.add(1); numbers2.add(new Object()); // won't compile! n = numbers2.get(0); • do seznamu numbers1 lze vložit libovolný objekt • při získávání objektů se spoléháme na to, že se jedná o číslo • do numbers2 nelze obecný objekt vložit, je nutné vložit číslo Motivace • Chceme seznam různých typů seznamů, tak jej vytvoříme následovně: List> listOfDifferentLists; • Máme problém — seznam čísel není seznamem objektů: List numbers = new ArrayList(); List general = numbers; // won't compile! List general2 = numbers; // solution 2  Do seznamu, který obsahuje nejvýše čísla lze vkládat pouze objekty, které jsou alespoň čísly. Žolíci (wildcards) I Generika poskytují nástroj zvaný žolík (wildcard) , který se zapisuje jako . List numbers = new ArrayList(); List general = numbers; // OK general.add("Not a number"); // won't compile! • List říká, že jde o seznam neznámých prvků. • Jelikož nevíme, jaké prvky v seznamu jsou, nemůžeme do něj ani žádné prvky přidávat. • Jedinou výjimkou je žádný prvek null, který lze přidat kamkoliv.  Abstraktní třída Number reprezentuje numerické primitivní typy (int, long, double, …) Žolíci (wildcards) II • Ze seznamu neznámých objektů můžeme prvky číst. • Každý prvek je alespoň instancí třídy Object: public static void printList(List list) {   for (Object e : list) {   System.out.println(e);   } } Žolíci a polymorfismus I Nasledující metoda dělá sumu ze seznamu čísel: 3 public static double sum(List numbers) {   double result = 0;   for (Number e : numbers) {   result += e.doubleValue();   }   return result; } ... List numbers = List.of(1,2,3); sum(numbers); // it works List integers = List.of(1,2,3); sum(integers); // won't compile! Žolíci a polymorfismus II • Integer je Number a přesto seznam List nelze použít! • Nechceme List, řešením je seznam neznámých prvků, které jsou nejvýše čísly. public static double sum(List numbers) { ... } • Toto použití žolíku má uplatnění i v rozhraní List, např. v metodě addAll: boolean addAll(Collection c); • Uvědomte si následující — žolík je zkratka pro neznámý prvek rozšiřující Object. Žolíci a dědičnost Další použití žolíků: • Parametrem metody je instance třídy, která je v hierarchii mezi třídou specifikovanou naším obecným prvkem E a třídou Object. • Například chceme setřídit množinu celých čísel. • Existuje třídění podle: ◦ hodnoty metody hashCode() — na úrovni třídy Object ◦ čísla — na úrovni třídy Number ◦ celého čísla — na úrovni třídy Integer • Konstruktor stromové setříděné mapy: public TreeSet(Comparator c); 4 Žolíci a více typů • Deklarace obecného rozhraní setříděné mapy: public interface SortedMap extends Map { ... } • Je-li třeba použít více nezávislých obecných typů, zapíšeme je do zobáčků jako seznam hodnot oddělených čárkou. • K je key, V je value. • Je možné použít i žolíků, viz následující příklad konstruktorů stromové mapy: public TreeMap(Map m); public TreeMap(SortedMap m); Generické metody • Pro používání generik a žolíků v metodách platí stále stejná pravidla. • Generická metoda = metoda parametrizována alespoň jedním obecným typem. • Obecný typ nějakým způsobem váže typy proměnných a/nebo návratové hodnoty metody. • Příklad statické metody, která přenese prvky z pole do seznamu (pole i seznam musí mít stejný typ): static void arrayToList(T[] array, List list) {   for (T o : array) list.add(o); } • Ve skutečnosti nemusí být seznam list téhož typu — stačí, aby jeho typ byl nadtřídou typu pole array. • Např. Integer[] array a List list ◦ prvky z pole do seznamu se dají kopírovat (i když typy nejsou stejné!), protože Integer je Number Generics metody vs. wildcards • Chceme, aby typ u generické metody spojoval parametry nebo parametr a návratovou hodnotu. • Ne úplně správné (funkční) použití generické metody: static void copy(List destination, List source); 5 • Lepší zápis, T spojuje dva parametry metody a přebytečné S je nahrazené žolíkem: static void copy(List destination, List source);  Metody jsou public, viditelnost je vynechána kvůli lepší přehlednosti. Pole • Pro pole nelze použít parametrizovanou třídu. • Při vkládání prvků do pole runtime systém kontroluje pouze typ vkládaného prvku. • Do pole řetězců bychom pak mohli vložit pole čísel a pod. // generic array creation error public T[] returnArray() {   return new T[10]; } • Jde však použít třídu s žolíkem, který není vázaný: List[] pole = new List[10]; Vícenásobná vazba generik I • Uvažujme následující metodu, která vyhledává maximální prvek kolekce. static Object max(Collection c); • Prvky kolekce musí implementovat rozhraní Comparable, což není syntaxí vůbec podchyceno. ◦ Zavolání této metody proto může vyvolat výjimku ClassCastException! • Chceme, aby prvky kolekce implementovali rozhraní Comparable. static > T max(Collection c); // if generics are removed static Comparable max(Collection c); // does not return Object! Vícenásobná vazba generik II • Signatura metody se změnila — má vracet Object, ale vrací Comparable! ◦ Metoda musí vracet Object kvůli zpětné kompatibilitě. 6 • Využijeme tedy vícenásobnou vazbu: static > T max (Collection c); • Po výmazu má metoda správnou signaturu, protože v úvahu se bere první zmíněná třída. • Obecně lze použít více vazeb, například když je obecný prvek implementací více rozhraní. Závěr • Generiky mají i další využití, například u reflexe. • Tohle však již překračuje rámec začátečnického seznamování s Javou. • Slidy vychází z materiálů ◦ Javy firmy Sun ◦ Generics in the Java Programming Language od Gilada Brachy 7