SCJP - Typy wyliczeniowe
Typy wyliczeniowe deklarujemy za pomocą słówka kluczowego enum bezpośrednio w pliku, w klasie, interfejsie albo wewnątrz innego typu wyliczeniowego, ale nie w metodzie. Typ wyliczeniowy, oprócz tego, że definiuje stałe wyliczeniowe może także zawierać konstruktory, zmienne i metody oraz klasy wewnętrzne i inne typy wyliczeniowe – prawie jak klasa, ale prawie robi ogromną różnicę.
Zacznijmy od stałych wyliczeniowych. Poniżej przykład deklaracji typu wyliczeniowego, który zawiera trzy takie stałe.
enum Size {
SMALL, LARGE, HUGE
}
Na tego typu deklaracjach kończy się wiedza wielu programistów, a to dopiero początek. Zacznijmy powoli, od modyfikatorów, jakich możemy użyć w deklaracji typu wyliczeniowego – dla deklaracji bezpośrednio w pliku są to tylko public oraz strictfp, a dla deklaracji wewnątrz innych typów dodatkowo private, protected i static. Ich znaczenie jest takie jak w przypadku klas i opisałem je w artykule "SCJP - Modyfikatory dla deklaracji klas". Idźmy dalej. Poniżej przykład deklaracji zawierającej stałe wyliczeniowe, metodę, zmienną i konstruktor. Zwróćmy uwagę na średnik po deklaracjach stałych wyliczeniowych – jest obowiązkowy, jeśli po tych deklaracjach znajduje się coś jeszcze. Zapamiętajmy też, że stałe wyliczeniowe muszą być zadeklarowane jako pierwsze, dopiero potem, po średniku następują kolejne deklaracje!
enum Size {
SMALL(1), LARGE(2), HUGE(3);
Size(int size) {
this.size = size;
}
public int getSize() {
return size;
}
private int size;
}
To, czym typy wyliczeniowe różnią się zdecydowanie od klas jest, że mogą one zawierać jedynie konstruktory prywatne. Nie jest możliwe utworzenie instancji typu wyliczeniowego w inny sposób, jak tylko poprzez deklarację stałej wyliczeniowej. Tak, stałe wyliczeniowe są takiego typu, w jakim je zadeklarowano i są to jedyne instancje tego typu. W powyższym przykładzie stałe Size.SMALL, Size.LARGE i Size.HUGE są zatem typu Size i są to jedyne instancje tego typu jakie kiedykolwiek będą występowały w przyrodzie, tj. w JVM – innych utworzyć się nie da, także używając refleksji czy operacji clone(). Oznacza to między innymi, że możemy używać operatora == zamiast operacji equals(). Mimo, że tak czy inaczej konstruktory typów wyliczeniowych są prywatne, możemy oznaczyć je słówkiem kluczowym private explicite. Jest to jedyny modyfikator, jakiego możemy użyć w deklaracji takiego konstruktora.
Zerknijmy teraz ponownie na deklarację stałych wyliczeniowych w powyższym przykładzie. Są to stałe i do dobrych praktyk należy, aby ich nazwy były pisane samymi wielkimi literami, ale nie jest to wymóg bezwzględny, może to być dowolny poprawny identyfikator – co to znaczy poprawny opisałem w artykule "SCJP - Poprawne identyfikatory i konwencje nazewnicze". Tuż po nazwie stałej możemy podać parametry dla wywołania konstruktora. Jeśli argumentów nie podano – tj. użyto samej nazwy, np. SMALL – to dla utworzenia instancji (stałej) wywoływany jest konstruktor bez-argumentowy. Tak jak w przypadku klas, możemy zdefiniować i użyć dowolny inny konstruktor.
Typy wyliczeniowe mogą implementować interfejsy, ale nie mogą dziedziczyć z innych typów wyliczeniowych. Istnieje jednak sposób na przedefiniowanie metod dla wybranych stałych wyliczeniowych, zupełnie jakby były one podtypami zawierającego typu wyliczeniowego. Po deklaracji stałej i jej ewentualnych parametrach dla konstruktora można umieścić ciało klasy anonimowej, która dla tej jednej instancji określi typ na bazie typu zawierającego. Spójrzmy na poniższy przykład.
enum Size {
SMALL {
public String getDescription() {
return "Taki calkiem maly";
}
},
LARGE {
public String getDescription() {
return "Dosyc duzy";
}
};
public abstract String getDescription();
}
Mimo, że nie możemy zadeklarować, że dany typ wyliczeniowy dziedziczy z innego typu, to jak najbardziej typy wyliczeniowe również należą do wspólnej hierarchii z klasą Object w korzeniu. Każdy typ wyliczeniowy E dziedziczy bowiem implicite z typu Enum<E>, a ten z typu Object. Oprócz metod odziedziczonych z Enum<E> każdy typ wyliczeniowy E ma zdefiniowane metody ‘public static E[] values()’ oraz ‘public static E valueOf(String name)’. Metoda values() zwraca tablicę stałych wyliczeniowych zdefiniowanych w typie E a metoda valueOf() służy do konwersji z typu String – zwraca instancję typu E (jedną ze stałych wyliczeniowych) o podanej nazwie.
Jeśli chodzi o deklaracje metod, to dozwolone są wszystkie te modyfikatory, co dla deklaracji w klasach, z jednym tylko obostrzeniem dla modyfikatora abstract – jeśli w typie wyliczeniowym zadeklarowano metodę abstrakcyjną, to musi być ona zdefiniowana przez każdą ze stałych wyliczeniowych w tym typie. Musi przy tym być zadeklarowana co najmniej jedna taka stała. Zmienne obowiązują te same zasady, co zmienne w klasach.
Było ciężko, ale się udało – zakończyliśmy niniejszym lekturę pierwszego rozdziału referowanej książki "SCJP Sun Certified Programmer for Java 5 Study Guide (Exam 310-055)", a to już mały sukces.