Generické třídy, výčtové typy a String


Materiály

Generické třídy

Generický typ je třída nebo rozhraní která je parametrizovaná typem. Princip generického datového typu demostrujeme na následující třídě Box.
    
    public class Box {
        private Object object;

        public void set(Object object) { this.object = object; }
        public Object get() { return object; }
    }
    

Práce se třídou Box není pohodlná. Neustále musíme kontrolovat typ atributu object.

    
    Box box = new Box();
    box.set(12);

    if (box.get() instanceof Integer) {
        int soucet = 10 + (Integer) box.get();
        System.out.print("Součet 10 + 12 = " + soucet);
    }
    

Tento problém řeší generické třídy a rozhraní. Definice generické třídy má následující syntaxi.

public class name<T1, T2, ..., Tn> { /* ... */ }

Generická verze třídy Box tedy vypadá následovně:

    
    /**
     * 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; }
    }
    

Konvence pojmenovávání parametrů

Parametry se pojmenovávají jedním velkým písmenem. Níže je uvedený seznam nejčastěji používaných jmen pro generické parametry.

Používání generické třídy

    
    Box<Integer> integerBox = new Box<Integer>();

    // Ekvivaletní deklarace
    Box<Integer> integerBox = new Box<>();
    integerBox.set(12);

    int soucet = 10 + box.get();
    System.out.print("Součet 10 + 12 = " + soucet);
    

Příklad generického typu s více parametry

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

    // Použití
    Pair<String, Integer> p1 = new OrderedPair<>("Even", 8);
    Pair<String, String>  p2 = new OrderedPair<>("hello", "world");
    

Omezení parametrů

Pokud bychom chtěli z nějakého důvodu omezit parametr T ve tříde Box pouze na čísla typu Integer, Float a Double. Třídu Box tedy upravíme následovně.

    

    public class Box<T extends Number> {
        private T t;

        public void set(T t) { this.t = t; }
        public T get() { return t; }
    }
    

Wildcards

Uvažujme metodu sum, která sečte všechna čísla v kolekci a metodu add která přidá prvky do kolekce. Metoda sum jako svůj argument bude přijímat pouze List<Integer>, List<Double> a List<Float>. Pomocí wildcard můžeme omezit parametr metody následovně:

U metody add chceme aby bylo možné pracovat s List<Integer>, List<Number> a List<Object>.
    
    // Omezení zhora
    public static double sumOfList(List<? extends Number> list) {
        double s = 0.0;
        for (Number n : list)
            s += n.doubleValue();
        return s;
    }

    // Omezení zdola
    public static void addNumbers(List<? super Integer> list) {
        for (int i = 1; i <= 10; i++) {
            list.add(i);
    }
}
    

Výčtové typy

Výčtový typ je speciální datový typ který může nabývat pouze předepsaných kosntant. Příkladem konstant mohou být dny v týdnu. Výčtový typ se definuje pomocí klíčového slova enum.

    
    // Definice výčtového typu
    public enum Day {
        SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
        THURSDAY, FRIDAY, SATURDAY
    }

        // Použití
    public class EnumTest {
        Day day;

        public EnumTest(Day day) {
            this.day = day;
        }

        public void tellItLikeItIs() {
            switch (day) {
                case MONDAY:
                    System.out.println("Mondays are bad.");
                    break;

                case FRIDAY:
                    System.out.println("Fridays are better.");
                    break;

                case SATURDAY: case SUNDAY:
                    System.out.println("Weekends are best.");
                    break;

                default:
                    System.out.println("Midweek days are so-so.");
                    break;
            }
    }
    

Výčtové typy mohou obsahovat atributy a kosntruktory. Konstruktor ovšem musí být private nebo package-private.

    
    public enum Planet {
        MERCURY (3.303e+23, 2.4397e6),
        VENUS   (4.869e+24, 6.0518e6),
        EARTH   (5.976e+24, 6.37814e6),
        MARS    (6.421e+23, 3.3972e6),
        JUPITER (1.9e+27,   7.1492e7),
        SATURN  (5.688e+26, 6.0268e7),
        URANUS  (8.686e+25, 2.5559e7),
        NEPTUNE (1.024e+26, 2.4746e7);

        private final double mass;   // in kilograms
        private final double radius; // in meters
        Planet(double mass, double radius) {
            this.mass = mass;
            this.radius = radius;
        }
        private double mass() { return mass; }
        private double radius() { return radius; }

        // universal gravitational constant  (m3 kg-1 s-2)
        public static final double G = 6.67300E-11;

        double surfaceGravity() {
            return G * mass / (radius * radius);
        }
        double surfaceWeight(double otherMass) {
            return otherMass * surfaceGravity();
        }
        public static void main(String[] args) {
            if (args.length != 1) {
                System.err.println("Usage: java Planet <earth_weight>");
                System.exit(-1);
            }
            double earthWeight = Double.parseDouble(args[0]);
            double mass = earthWeight/EARTH.surfaceGravity();
            for (Planet p : Planet.values())
               System.out.printf("Your weight on %s is %f%n",
                                 p, p.surfaceWeight(mass));
        }
    }
    

String

   
    String s = "Hello World!";

    // Podřetězec
    System.out.println(s.substring(6)); // -> "World"

    // Rozdělení řetezce
    String[] word = s.split(" "); // [Hello, World!]

    System.out.println(s.replace("o", "0")); // Hell0 W0rld!

    s += " It's me."; // "Hello World! It's me."

    // Většina výše uvedených operací je neefektivních.
    // Pro pokročilo práci s řetezci je lepší použít StringBuilder

    StringBuilder stringBuilder = new StringBuilder();

    // Spojování řetězců
    stringBuilder.append("Hello !");

    // Nahrazení
    stringBuilder.replace(5, 6, " World");

    // Převod instance třidy StringBuilder na String se provádí pomocí metody toString()
    System.out.println(stringBuilder.toString());
   

Úkoly

  1. Implementujte generickou třídu NumericPriorityQueue která reprezentuje prioritní frontu. Prvky ve frontě jsou řazeny od nejmenšího po největší. Vyhněte se metodám pro třídění kolekce List. Třída bude mít následující metody

        
    
            // Vloží prvek do fronty
            void enqueue(T item);
    
            // Získá první prvek z fronty, pokud je fronta prázdná vrací null.
            T dequeue();
    
            // Sečtě všechna čísla ve frontě.
            double sum();
    
            // Vrátí délku seznamu.
            int size();
        
    

    Zvolte vhodné omezení typu pro konstruktor.

  2. Implementujte statické metody void repeatChar1(char c, int count) a void repearChar2(char c, int count), které vytvoří String obsahují znak c o délce count. Metoda repeatChar1 bude k sestavování požívat spojování řetezců pomocí += a metoda repeatChar1 použije třídu StringBuilder. Jako argument count zvolte velké číslo a pomocí System.currentTimeMillis() porovnejte obě metody.

  3. Napište třídu AnimalFarm evidující informace o zvířatech na statku. Třída bude mít dvě metody: add, která přidá do seznamu zvíře a o něm následující informace: jméno, druh zvířete, pohlaví a metodu list, která vypíše seznam zvířat ve tvaru:

        Alík je pes a dělá "haf-haf".
        Bobík je kačena a dělá "ga-ga".
        Chubaka je fena a dělá "haf-haf".
        Donald je kačer a dělá "ga-ga".
    

    Navrhěte vhodné výčtové typy pro reprezentaci zvířat.

Úkoly posílejte na email r.vyjidacek@gmail.com s předmětem ZP3JV04. Termín odevzdání do půlnoci 25. 10. 2017. Odevzdávejte pouze zdrojové kódy v archivu zip.