Warning
This page is located in archive. Go to the latest version of this course pages. Go the latest version of this page.

Výčtový typ - Enum

Pro účely karetních her zkusme porovnat reprezentaci barev karet pomocí konstant a pomocí enum. Všechny úkoly zkuste nejprve pomocí konstant:

  1. Vytvořte třídu karta, která bude mít konstruktor obsahující hodnotu a barvu karty,
  2. 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<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
    }
}

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

Ukázkový kód:

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

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

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.

courses/b0b36pjv/tutorials/04/enum.txt · Last modified: 2021/03/03 11:04 by seredlad