====== 3 - Polymorfismus, delegace, enum a šablony ====== * pro vyučující: [[:courses:a0b36pr2:internal:tutorialinstruction:03:start|]] ===== Náplň cvičení ===== Pokračujme na příkladu z minulé hodiny. Osobák vozí různé typy kol, ale na každý typ kola potřebuje speciální držák. Kapacita vozu je 4 kola. ==== Úkol 1. ==== Navrhněte třídu držák, ta bude držet instanci daného typu kola (delegace). * BicycleHolder * MountainBikeHolder extends BicycleHolder * RoadBikeHolder extends BicycleHolder Nápověda public class MountainBikeHolder extends BicycleHolder { public MountainBikeHolder(MountainBike bicycle) { super(bicycle); } } ==== Úkol 2. ==== Navrhněte vozidlo ''Car'', které bude přijímat kola různých typů a dle jejich typu bude vybírat vhodný držák, a to podobně jako jako se kola předávala do servisu (v minulém cvičení), tedy metodami: * ''accept(Bicycle)'' * ''accept(MountainBike)'' * ''accept(RoadBike)'' Kola se ve vozidle budou ukládat jen s pomocí držáku a to do seznamu/listu: List carHolders = new ArrayList(4); === Nápověda === Generalizujte visitor pro auto i opravnu přes rozhraní ''Visitor'', upravte kola ať je může navštívit toto rozhraní public interface BicycleVisitor { public void accept(Bicycle b); public void accept(MountainBike b); public void accept(RoadBike b); } public class Bicycle { .. public void visit(BicycleVisitor servis) { servis.accept(this); // i v dalsich tridach } } ==== Úkol 3. === Každé kolo bude mít danou barvu, evidujme jen dobře definovaný počet barev výčtovým typem. Výčtovému typu přidejme pro každou barvu též atributy: název barvy a identifikační číslo. ==== Úkol 4. ==== Navrhněte generickou třídu ''AnyHolder'', která odstraní hierarchii z úkolu 1. Použijte ji ve druhém autě ''AnyHolderCar'' (původní ne-generickou implementaci ponechte, ať máte srovnání před očima). ===== Řešení ===== Řešení celého cvičení ke stažení: {{:courses:a0b36pr2:labs:lab03-solved.zip|zde}} ===== Pomůcky ===== ==== ENUM ==== Pro účely karetních her zkusme porovnat reprezentaci barev karet pomocí konstant a pomocí enum. Všechny úkoly zkuste nejprve pomocí konstant: - Vytvořte třídu karta, která bude mít konstruktor obsahující hodnotu a barvu karty, - bude obsahovat metodu toString, která textově vypíše hodnotu a barvu karty. A nyní pomocí ENUM, pro načtení hodnot můžete použít static import. Napište metody pro výpis a výpis v angličtině (diamonds (♦), spades (♠), hearts (♥) and clubs (♣)). Kde jsou nebezpečí používání konstant? import java.util.*; public class Card { public enum Rank { DEUCE, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, ACE } public enum Suit { CLUBS, DIAMONDS, HEARTS, SPADES } private final Rank rank; private final Suit suit; private Card(Rank rank, Suit suit) { this.rank = rank; this.suit = suit; } public Rank rank() { return rank; } public Suit suit() { return suit; } public String toString() { return rank + " of " + suit; } private static final List protoDeck = new ArrayList(); // Initialize prototype deck static { for (Suit suit : Suit.values()) for (Rank rank : Rank.values()) protoDeck.add(new Card(rank, suit)); } public static ArrayList newDeck() { return new ArrayList(protoDeck); // Return copy of prototype deck } } ==== Enum jak ho neznáme ==== Řekněme, že chceme pracovat s elementy, které je možné řadit vzestupně nebo sestupně. Mějme následující výchozí Enum: public enum Ordering { ASCENDING, DESCENDING; } === Atributy === Enum může mít atributy, stejně jako jakákoliv jiná třída. Tyto atributy lze nastavit v konstruktoru, který lze definovat. Deklarace jednotlivých prvků jsou vlastně volání konstruktoru (akorát když žádný není, vynecháme závorky) a lze tak předat atributům hodnoty. Uložme si pro každé seřazení jeho zkratku: public enum Ordering { ASCENDING("ASC"), DESCENDING("DESC"); public final String acronym; private Ordering(String acronym) { this.acronym = acronym; } } Atribut můžete použít stejně jako u jakékoli jiné třídy, např.: Ordering[] ords = Ordering.values(); for (Ordering o : ords) { System.out.println(o.acronym + " - " + o); } Produkuje: ASC - ASCENDING DESC - DESCENDING === Metody === Enum může mít metody, stejně jako jakákoliv jiná třída. A jelikož i Enum je potomkem třídy Object (stejně jako všechny třídy v javě), ukažme to na metodě ''toString()'': public enum Ordering { ASCENDING("ASC"), DESCENDING("DESC"); public final String acronym; private Ordering(String acronym) { this.acronym = acronym; } @Override public String toString() { return this.name() + "(" + acronym + ")"; } } Následující kód for (Ordering o : Ordering.values()) { System.out.println(o); } pak produkuje ASCENDING(ASC) DESCENDING(DESC) === Rozhraní === Stejně jako jakákoliv jiná třída, i Enum může implementovat rozhraní (interface). Implementujme na našem ''Ordering'' rozhraní ''java.util.Comparator'' pro ''Integer''. Máme dvě možnosti. == 1. Speciální implementace pro každý prvek == Každý prvek si implementuje vlastní verze metod rozhraní: public enum Ordering implements Comparator { ASCENDING { @Override public int compare(Integer o1, Integer o2) { return o1.compareTo(o2); } }, DESCENDING { @Override public int compare(Integer o1, Integer o2) { return o2.compareTo(o1); } }; } Ukázkový kód: List elementsBase = Arrays.asList(3, 1, 40, 42, 42, 51, -134, 0, 0); List sortedAsc = new ArrayList(elementsBase); Collections.sort(sortedAsc, Ordering.ASCENDING); List sortedDesc = new ArrayList(elementsBase); Collections.sort(sortedDesc, Ordering.DESCENDING); System.out.println(sortedAsc); System.out.println(sortedDesc); Produkuje následující výstup: ascending: [-134, 0, 0, 1, 3, 40, 42, 42, 51] descending: [51, 42, 42, 40, 3, 1, 0, 0, -134] == 2. Jediná společná implementace s vnitřním rozhodováním na základě ''this'' == Metody rozhraní jsou implementovány pouze jednou, na stejné úrovni jako např. konstruktor a až uvnitř nich se rozhodneme, co budeme dělat na základě toho, co je vlastně ''this'': public enum Ordering implements Comparator { ASCENDING, DESCENDING; @Override public int compare(Integer o1, Integer o2) { switch (this) { case ASCENDING: return o1.compareTo(o2); case DESCENDING: return o2.compareTo(o1); } throw new IllegalStateException(); // we can never reach this code but without it // compiler complains that the method might not // return anything } } Chová se totožně jako předchozí varianta. Nicméně, **tento postup se nedoporučuje.** Navíc ani není moc hezký (nedosažitelná výjimka na konci) a není to správně objektové. === Ještě jedna poznámka k metodám === Metody samotné lze také implementovat pro každý prvek zvlášť, jako jsme viděli u rozhraní. Náš ''toString()'' by tedy mohl vypadat i takto: public enum Ordering { ASCENDING { @Override public String toString() { return this.name() + "(ASC)"; } }, DESCENDING { @Override public String toString() { return this.name() + "(DESC)"; } }; } Kterou ze dvou variant použít, je otázka. Obecné pravidlo by se dalo vyložit asi takto: pokud je chování metody specifické pro každý prvek (dá se zhruba říct i jako "bylo by třeba dělat ''switch(this)''"), je vhodné ji implementovat pro každý prvek zvlášť. Pokud je chování pro všechny prvky víceméně shodné (či "není třeba dělat ''switch(this)''"), je vhodné ji implementovat na úrovni Enumu. === Další studium === Pro maximální porozumění (nejen) Enumům doporučuji Chapter 6, Items 30-34 z knihy Effective Java, 2nd Edition. ==== Generika ==== Generika umožňují vytvářet třídy a metody "on demand" parametrizované nějakou jinou třídou (ale nikoliv např. číslem, což je možné např. v C++, kde se to nazývá "templates" neboli šablony). === Generická třída a rozhraní === Jednoduchý příklad: /** * Generic version of the Box class. * @param the type of the value being boxed */ public class Box { // T stands for "Type" private T t; public void set(T t) { this.t = t; } public T get() { return t; } } A zde volání Box integerBox = new Box(); Jiný a lepší příklad public interface Pair { public K getKey(); public V getValue(); } public class OrderedPair implements Pair { private K key; private V value; public OrderedPair(K key, V value) { this.key = key; this.value = value; } public K getKey() { return key; } public V getValue() { return value; } } inicializace OrderedPair class: Pair p1 = new OrderedPair("Even", 8); Pair p2 = new OrderedPair("hello", "world"); Java 7 zkrátí na OrderedPair p1 = new OrderedPair<>("Even", 8); OrderedPair p2 = new OrderedPair<>("hello", "world"); Pro ty co se neztratili OrderedPair> p = new OrderedPair<>("primes", new Box(...)); === Generická metoda === Nemusí být generická celá třída, generickou lze udělat i samotnou metodu: public class ListCreator { public static List createList(T ... elements) { List list = new ArrayList(elements.length); for (T element : elements) { list.add(element); } return list; } } Ono '''' před signaturou metody je ekvivalentní stejnému '''' za názvem třídy, akorát negeneralizujeme celou třídu, ale samotnou metodu. Když generické metody voláme, můžeme buď nechat typ odvodit: List ints = ListCreator.createList(1, 3); System.out.println(ints); System.out.println("Types: " + ints.get(0).getClass().getSimpleName() + ", " +ints.get(1).getClass().getSimpleName()); List strings = ListCreator.createList("ab", "cd"); System.out.println(strings); System.out.println("Types: " + strings.get(0).getClass().getSimpleName() + ", " +strings.get(1).getClass().getSimpleName()); Produkuje [1, 3] Types: Integer, Integer [ab, cd] Types: String, String Nebo jej ručně specifikovat voláním ve tvaru: List ints = ListCreator.createList(1, 3); Zde ale postrádá smysl, protože není jiná možnost než tato. ===== Ve zbylém čase, nebo práce na doma ===== ==== Adresář ==== * Vytvořte si **telefonní seznam** obsahující položky ve formátu jméno, příjmení, telefonní číslo, email, ulice, číslo popisné, město, PSČ. * Pro snazší implementaci použijte jako datovou strukturu třídu **ArrayList**. Použijte **generiku** a najděte výhody tohoto přístupu. Dodržujte **zapouzdření** a přístup k datům umožněte pouze přes rozhraní své třídy. * Vytvořte metody pro vložení záznamu, odebrání záznamu a zjištění, zda tam daný záznam je.