Java

Enum

  • 2008-04-29 22:32
  • 4 komentarze
  • 13977 odsłon
  • Oceń ten tekst jako pierwszy
Spis treści

     1 Problem
     2 Definicja typu enum
     3 Enum pola i metody
          3.1 pola
          3.2 Metody własne enum
          3.3 Definiowanie metod
     4 Enum w pętlach i przełącznikach (switch)
               4.1 Blok switch
               4.2 Pętle
     5 Przykładowy program


Problem


Wiele programów w javie korzysta ze składni "stałych sterujących":
public class A {
    public static final int STAN_A = 0;
 
    public static final int STAN_B = 1;
 
    public void metoda( int stan ) {
        if ( stan == STAN_A ) {
            //......
        }
        else if ( stan == STAN_B ) {
            //......
        }
        else {
            throw new IllegalArgumentException("nieprawidłowy stan!");
        }
    }
}


Takie podejście, choć w ogólności słuszne, jest obarczone wieloma poważnymi wadami.
  • brak ochrony typu - nic nie stoi na przeszkodzie by wywołać metodę z parametrem będącym sumą STAN_A i STAN_B. Taki zapis nie ma sensu. Jeżeli dodatkowo będzie brakowało ostatniego bloku, to znaczy funkcja nie będzie zwracała wyjątku gdy parametr będzie nieprawidłowy, programista może zostać zaskoczony pozbawionym sensu zachowaniem aplikacji. Nic nie stoi też na przeszkodzie by przekazać parametr typu float lub double (oczywiście rzutujemy).
  • konieczność korzystania z przedrostków by się po prostu nie pogubić. Dodatkowo przedrostki i nazwy zmiennych wystarczają do określenia co chcemy zrobić, a tak musimy jeszcze definiować zmienne.
  • sztywne zaszycie wartości w kodzie. Zmienne STAN_A i STAN_B są zmiennymi czasu kompilacji co oznacza, że
    • w miejscach wywołania kompilator podstawia ich wartości a nie referencje do nich, a tym samym
    • jakakolwiek zmiana ich wartości lub dodanie nowej, STAN_C, wymaga powtórnej kompilacji kodu wraz z cały kodem klientów z nich korzystających.
  • wartości nie są znaczące - jeżeli wypiszemy wartości STAN_A i STAN_B to otrzymamy nic nie znaczące cyfry. Dopiero lektura dokumentacji umożliwi identyfikację znaczcenia każdej z cyfr.

W celu rozwiązania tych problemów w Javie 5 wprowadzono typ enum.

Definicja typu enum


Typ enum jest typem wyliczeniowym, literałem, który jest traktowany jak klasa specjalna zawierająca w swojej definicji wszystkie możliwe do stworzenia instancje, obiekty.

Typ enum wygląda bardzo podobnie jak w C/C++/C# lecz jest bardziej rozbudowany. Najprostsza wersja ma postać:

enum Stany{ A, B}


Sam typ enum jest traktowany tak jak klasa i może być przetwarzany w ten sam sposób. Jest on porównywalny (Comparable) i serializowany (Serializable).

Enum pola i metody


pola


Typ enum w języku java pozwala, w przeciwieństwie do swoich odpowiedników z innych języków, definiować pola i metody dla każdej z wartości. Wynika to z "klasowej" charakterystyki enumów. Prawidłowym jest zatem zapis:

enum Wartosci {
    A, B;
 
    public String name;
 
    protected String value;
 
    private int number;
}


Metody własne enum


Typ enum zawiera trzy metody.

static values() - zwraca tablicę zawierającą wszystkie obiekty enum. W naszym przypadku będą to A i B
static valueOf(String) - zwraca wartość enuma, w postaci obiektu enum, dla klucza określonego jako String
static valueOf(Class< T >, String) - zwraca wartość enuma, w postaci obiektu enum, dla klucza określonego jako String z klasy enumów T.

przykładowy kod:
System.out.println(Wartosci.values());
System.out.println(Wartosci.valueOf( "A" ));
System.out.println(Wartosci.valueOf( W.class, "B" ));

zwróci:
[Lenums.Wartosci;@13e8d89
A
B


Definiowanie metod


Enum jest traktowany jak klasa zatem może posiadać też metody. Metody definiuje się tak samo jak w przypadku zwykłych klas. Możemy zatem napisać:

enum Wartosci {
    A, B("enum B");
 
    //...
 
    public void metoda1(){
            //...
    }
 
    protected void metoda2(){
            //...
    }
 
    private void metoda3(){
            //...
    }
 
    private Wartosci(){
       //...
    }
 
    private Wartosci(String s){
            name = s;
    }
}


Należy zwrócić uwagę na dwie istotne rzeczy.
Po pierwsze konstruktor może mieć albo modyfikator private albo nie mieć modyfikatora (package-private). Może on przyjmować parametry, ale nie można wywoływać go bezpośrednio (należy użyć do tego stałych zdefiniowanych w typie). W powyższym przykładzie napisanie Wartosci.B stworzy obiekt typu Wartości i przekaże do konstruktora String "enum B". Nie można natomiast napisać new Wartosci("jakis napis").
Po drugie metody mogą mieć dowolny modyfikator dostępu. Dostęp do nich realizowany jest tak samo jak w przypadku zwykłych obiektów.

Enum w pętlach i przełącznikach (switch)


Do tych czas omówiliśmy jak tworzy się enum oraz co można, a czego nie można zdefiniować w jego ramach. Jednak prawdziwa siła enumów leży nie w samym sposobie ich definiowania lecz w możliwościach ich wykorzystania.

Blok switch


Na początku wspomniany był przykład "zmiennej sterującej" w danej klasie. Bardzo często zmienne takie wykorzystywane są w blokach switch. Co jednak stanie się gdy wartość przekazana do takiego bloku jest inna niż te, które zdefiniowano? Oczywiście zostanie wywołany fragment po instrukcji default, co może spowodować nieintuicyjne działanie aplikacji lub w gorszym wypadku pojawienie się wyjątku na przykład IllegalArgumentException. Dlaczego tak się dzieje? Jest to wynik problemów związanych ze słabą kontrolą typu zmiennej, a w rzeczywistości brakiem kontroli jej wartości. Przyjrzyjmy się takiemu oto fragmentowi:

void metoda1(){
        metoda (2);
}
 
void metoda(int i){
        switch (i) {
        case 0:
                break;
        case 1:
                break;
        default:
                throw new IllegalArgumentException();
        }
}


Z punktu widzenia kompilatora, a w zasadzie parsera kodu, wszystko jest poprawne. metoda() została wywołana z prawidłowym parametrem typu int, zatem można przyjąć, że programista wie co robi gdy wywołuje tą metodę. W rzeczywistości wywołanie spowoduje błąd. Jak widać w procesie kompilacji nie udało się, i nie ma na to metody, wychwycić błędnego parametru. Jeżeli chcemy, aby wszytko przebiegło poprawnie należało by napisać kod, który pozwalał by na ścisłą kontrolę typu i wyłapywał tego typu błędy już w trakcie kompilacji.

Typ enum może być użyty w bloku switch

Ten fakt pozwala nam na napisanie poprawnego kodu:

void metoda1(){
        metoda(Wartosci.A);
}
 
void metoda(Wartosci w){
        switch (w) {
        case A:
                break;
        case B:
                break;
        }
}


To rozwiązanie jest dla nas podwójnie korzystne. Z jednej strony pozwala uniknąć błędu złego argumentu, po drugie pozwoli wyłapać potencjalne błędy już na poziomie kompilacji dzięki kontroli typu.

Pętle


Enumy mogą zostać też użyte do reprezentowania dobrze określonych (przeliczalnych), skończonych i uporządkowanych zbiorów, na przykład talii kart, układu planetarnego, czy układu okresowego. A jak pracujemy na zbiorach to zawsze spotykamy się z problemem wykonania jakiejś operacji na wszystkich elementach tego zbioru.

Typ enum może być użyty w pętli for w wersji z iteratorem

przykładowy kod:
for(Wartosci w : Wartosci.values()){
        System.out.println(w.value);
}


Przykładowy program


Na zakończenie chciałbym przedstawić przykładowy program, w którym wykorzystano typ enum. Program wyznaczy ile na innych planetach będzie ważyło 100kg:

public class Masa {
 
        public static void main(String[] args) {
                double masaZiemi = 100;
                double masa = masaZiemi / Planeta.ZIEMIA.przeliczGrawitacje();
                for (Planeta p : Planeta.values())
                        System.out.printf("twoja masa na %s wynosi %f kilogramów\n", p, p
                                        .przeliczMasy(masa));
 
        }
 
        public enum Planeta {
                MERKURY(3.303e+23, 2.4397e6), WENUS(4.869e+24, 6.0518e6), ZIEMIA(
                                5.976e+24, 6.37814e6), MARS(6.421e+23, 3.3972e6), JOWISZ(
                                1.9e+27, 7.1492e7), SATURN(5.688e+26, 6.0268e7), URAN(
                                8.686e+25, 2.5559e7), NEPTUN(1.024e+26, 2.4746e7), PLUTON(
                                1.27e+22, 1.137e6);
 
                private final double masa; // w kilogramach
                private final double promien; // w metrach
 
                Planeta(double masa, double promien) {
                        this.masa = masa;
                        this.promien = promien;
                }
 
                public double masa() {
                        return masa;
                }
 
                public double radius() {
                        return promien;
                }
 
                // uniwersalna stała grawitacyjna (m3 kg-1 s-2)
                public static final double G = 6.67300E-11;
 
                public double przeliczGrawitacje() {
                        return G * masa / (promien * promien);
                }
 
                public double przeliczMasy(double innaMasa) {
                        return innaMasa * przliczGrawitacje();
                }
        }
 
}


4 komentarze

mgs_saladin 2010-07-12 16:45

return innaMasa * przliczGrawitacje(); // literowka :)

von_Vogt 2008-04-16 10:10

Enum posiada więcej metod. Posiada trzy metody statyczne, które można wywołać bez konieczności tworzenia obiektu.
Dodatkowo ma:
public final ordinal()- zwraca wartość int informującą o miejscu deklaracji danej stałej w wyliczeniu
public final int compareTo(E o) - porównuje enum z obiektem podanym jako argument pod względem pozycji w wyliczeniu.
Pozostałe: Equals, hasCode, name, toString... Wszystkie klasy Enum.

Artykuł pominął kwestię tworzenia obiektów danej klasy Enum.

Koziołek 2007-11-15 18:44

Ma i to nawet jeszcze większe, ale jakoś nie opisałem... może kiedyś

sznurek 2007-11-15 15:58

Coż powiedzieć - brawo! Oby tak dalej. Sam mam na dysku niedokończony artykuł o Apache Ant, ale jakoś brak mi weny by go dokończyć...

Nawet nie wiedziałem, że Enum w Javie ma takie możliwości ;)