Search
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.
Navrhněte třídu držák, ta bude držet instanci daného typu kola (delegace).
Nápověda
public class MountainBikeHolder extends BicycleHolder { public MountainBikeHolder(MountainBike bicycle) { super(bicycle); } }
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:
Car
accept(Bicycle)
accept(MountainBike)
accept(RoadBike)
Kola se ve vozidle budou ukládat jen s pomocí držáku a to do seznamu/listu:
List<BicycleHolder> carHolders = new ArrayList<BicycleHolder>(4);
Generalizujte visitor pro auto i opravnu přes rozhraní Visitor, upravte kola ať je může navštívit toto rozhraní
Visitor
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 } }
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.
Navrhněte generickou třídu AnyHolder<T>, 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).
AnyHolder<T>
AnyHolderCar
Řešení celého cvičení ke stažení: zde
Pro účely karetních her zkusme porovnat reprezentaci barev karet pomocí konstant a pomocí enum. Všechny úkoly zkuste nejprve pomocí konstant:
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<Card> protoDeck = new ArrayList<Card>(); // Initialize prototype deck static { for (Suit suit : Suit.values()) for (Rank rank : Rank.values()) protoDeck.add(new Card(rank, suit)); } public static ArrayList<Card> newDeck() { return new ArrayList<Card>(protoDeck); // Return copy of prototype deck } }
Ř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; }
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; } }
Ordering[] ords = Ordering.values(); for (Ordering o : ords) { System.out.println(o.acronym + " - " + o); }
ASC - ASCENDING DESC - DESCENDING
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():
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 + ")"; } }
for (Ordering o : Ordering.values()) { System.out.println(o); }
ASCENDING(ASC) DESCENDING(DESC)
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.
Ordering
java.util.Comparator
Integer
Každý prvek si implementuje vlastní verze metod rozhraní:
public enum Ordering implements Comparator<Integer> { 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); } }; }
List<Integer> elementsBase = Arrays.asList(3, 1, 40, 42, 42, 51, -134, 0, 0); List<Integer> sortedAsc = new ArrayList<Integer>(elementsBase); Collections.sort(sortedAsc, Ordering.ASCENDING); List<Integer> sortedDesc = new ArrayList<Integer>(elementsBase); Collections.sort(sortedDesc, Ordering.DESCENDING); System.out.println(sortedAsc); System.out.println(sortedDesc);
ascending: [-134, 0, 0, 1, 3, 40, 42, 42, 51] descending: [51, 42, 42, 40, 3, 1, 0, 0, -134]
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:
this
public enum Ordering implements Comparator<Integer> { 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 } }
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)"; } }; }
switch(this)
Pro maximální porozumění (nejen) Enumům doporučuji Chapter 6, Items 30-34 z knihy Effective Java, 2nd Edition.
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).
Jednoduchý příklad:
/** * Generic version of the Box class. * @param <T> the type of the value being boxed */ public class Box<T> { // T stands for "Type" private T t; public void set(T t) { this.t = t; } public T get() { return t; } }
Box<Integer> integerBox = new Box<Integer>();
public interface Pair<K, V> { public K getKey(); public V getValue(); } public class OrderedPair<K, V> implements Pair<K, V> { 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; } }
Pair<String, Integer> p1 = new OrderedPair<String, Integer>("Even", 8); Pair<String, String> p2 = new OrderedPair<String, String>("hello", "world");
OrderedPair<String, Integer> p1 = new OrderedPair<>("Even", 8); OrderedPair<String, String> p2 = new OrderedPair<>("hello", "world");
OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<Integer>(...));
Nemusí být generická celá třída, generickou lze udělat i samotnou metodu:
public class ListCreator { public static <T> List<T> createList(T ... elements) { List<T> list = new ArrayList<T>(elements.length); for (T element : elements) { list.add(element); } return list; } }
<T>
Když generické metody voláme, můžeme buď nechat typ odvodit:
List<Integer> ints = ListCreator.createList(1, 3); System.out.println(ints); System.out.println("Types: " + ints.get(0).getClass().getSimpleName() + ", " +ints.get(1).getClass().getSimpleName()); List<String> strings = ListCreator.createList("ab", "cd"); System.out.println(strings); System.out.println("Types: " + strings.get(0).getClass().getSimpleName() + ", " +strings.get(1).getClass().getSimpleName());
[1, 3] Types: Integer, Integer [ab, cd] Types: String, String
List<Integer> ints = ListCreator.<Integer>createList(1, 3);