Enum
Koziołek
1 Problem
2 Definicja typu enum
3 Pola i metody o typie enum
3.1 Pola
3.2 Metody własne typu enum
3.3 Definiowanie metod
4 Typ enum
w pętlach i przełącznikach (switch
)
4.3.1 Blok switch
4.3.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
iSTAN_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 – to 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
iSTAN_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 (np.
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
iSTAN_B
, to otrzymamy nic nie znaczące cyfry. Dopiero lektura dokumentacji umożliwi identyfikację ich znaczenia.
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 obiektów.
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).
Pola i metody o typie enum
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 tego typu. Poniższy zapis jest więc prawidłowy:
enum Wartosci {
A, B;
public String name;
protected String value;
private int number;
}
Metody własne typu enum
Typ enum
zawiera trzy metody:
-
static values()
– zwraca tablicę zawierającą wszystkie obiektyenum
. W naszym przypadku będą toA
iB
. -
static valueOf(String)
– zwraca wartość enuma w postaci obiektuenum
, dla klucza o typieString
. -
static valueOf(Class< T >, String)
– zwraca wartość typuenum
w postaci obiektuenum
, dla klucza o typieString
z klasy enumówT
.
Przykładowy kod:
System.out.println(Wartosci.values());
System.out.println(Wartosci.valueOf( "A" ));
System.out.println(Wartosci.valueOf( W.class, "B" ));
zwróci (w konsoli):
[Lenums.Wartosci;@13e8d89
A
B
Definiowanie metod
Typ 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 rzeczy. Po pierwsze, konstruktor typu enum
może mieć albo modyfikator private
, albo nie mieć modyfikatora (tzw. "package-private"). Może on przyjmować parametry, ale nie można wywoływać go bezpośrednio (należy użyć do tego zdefiniowanych stałych). W powyższym przykładzie napisanie Wartosci.B
stworzy obiekt o typie Wartosci
i przekaże do konstruktora ciąg znaków "enum B" (obiekt o typie String
). 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.
Typ enum
w pętlach i przełącznikach (switch
)
Dotychczas omówiliśmy, jak tworzy się typ enum
, oraz co można, a czego nie można definiować 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 się stanie, 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 ją wywołuje. 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 chcielibyśmy, aby wszytko przebiegło poprawnie, należałoby napisać kod, który pozwalałby na ścisłą kontrolę typu i wyłapywał takie 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, a po drugie pozwoli wyłapać potencjalne błędy już na poziomie kompilacji dzięki kontroli typu.
Pętle
Typ enum
może zostać też użyty 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 jeśli 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 korzystam z typu enum
. Program wyznaczy, ile na innych planetach będzie ważyło ciało, które na ziemi waży 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();
}
}
}
ten enum to trochę jak factor w R.
return innaMasa * przliczGrawitacje(); // literowka :)
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.
Ma i to nawet jeszcze większe, ale jakoś nie opisałem... może kiedyś
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 ;)