Stały zbiór nazwanych wartości
Niektóre wartości mają sens tylko jako element małej, znanej listy: dni tygodnia, kolory w talii kart, stany zamówienia. Można je modelować kodami int (0 dla oczekującego, 1 dla wysłanego) albo napisami ("PENDING", "SHIPPED"), ale nic nie powstrzyma wywołującego przed przekazaniem 7 albo "PNEDING". enum zamyka tę lukę: sam typ dopuszcza tylko zadeklarowane wartości.
Day to zupełnie nowy typ. Zmienna typu Day może przechowywać dokładnie jedną z tych siedmiu stałych albo null i nic więcej. Kompilator odrzuca Day d = "WEDNESDAY"; i Day d = 2;. Ta gwarancja to cały sens enuma.
Każda stała (Day.MONDAY i kolejne) to pojedyncza, współdzielona instancja tworzona raz. Ponieważ zawsze istnieje tylko jeden MONDAY, enumy porównuje się przez ==, a nie equals: to ten sam obiekt.
switch na enumie
Enumy naturalnie łączą się ze switch. W środku switcha nie trzeba nawet poprzedzać stałych nazwą typu:
W switch pisz case SATURDAY, a nie case Day.SATURDAY: Java sama wywnioskuje typ. Wielka przewaga nad switchem na napisie lub int polega na tym, że gdy później dodasz nową stałą, IDE i sprawdzanie kompletności mogą wskazać każdy switch, który jej nie obsługuje.
Pętla po wszystkich stałych
Każdy enum automatycznie dostaje statyczną metodę values(), która zwraca wszystkie stałe w kolejności deklaracji, oraz ordinal(), która podaje pozycję stałej liczoną od zera:
name() zwraca identyfikator stałej jako String, a values() świetnie nadaje się do budowania menu albo przechodzenia po opcjach. Jedno ostrzeżenie: nie zapisuj ordinal() nigdzie na stałe. Jeśli ktoś później zmieni kolejność stałych, numery się przesuną, a zapisane dane się zepsują. Traktuj ordinal() jako szczegół implementacji, a nie stabilny identyfikator.
Enumy to prawdziwe klasy: pola i metody
Tu enumy w Javie wykraczają poza enumy z nazwanymi liczbami znane z innych języków. Każda stała może nieść dane przekazywane przez konstruktor, a enum może mieć metody. Najpierw wypisujesz stałe, każdej podajesz argumenty konstruktora, a potem po średniku deklarujesz pola, konstruktor i metody.
Zwróć uwagę na strukturę: najpierw stałe zakończone średnikiem, potem reszta klasy. Konstruktor jest niejawnie private: nigdy nie napiszesz new Planet(...), bo jedyne dozwolone instancje to te zadeklarowane na górze. Pola final czynią każdą stałą niezmienną, a właśnie tego chcesz od współdzielonych singletonów.
Z napisu i z powrotem
Aby zamienić napis na pasującą stałą, użyj automatycznie generowanej metody valueOf:
valueOf dopasowuje nazwę stałej dokładnie, łącznie z wielkością liter, i rzuca IllegalArgumentException, gdy nic nie pasuje. To częsta pułapka: "shipped" nie pasuje do SHIPPED. Przy parsowaniu danych od użytkownika lub z zewnątrz najpierw sprowadź tekst do jednej wielkości liter (input.toUpperCase()) i umieść wywołanie w bloku try/catch, bo inaczej program wysypie się przy pierwszej złej wartości.
Zachowanie dla każdej stałej
Czasem każda stała musi się inaczej zachowywać, a nie tylko przechowywać inne dane. Możesz dać enumowi metodę abstrakcyjną i pozwolić każdej stałej dostarczyć własną implementację w małym ciele:
Nazywa się to czasem wzorcem „metody specyficznej dla stałej”. Zastępuje rozbudowany switch po stałych zachowaniem przypiętym bezpośrednio do każdej z nich. Gdy dodasz nową operację, kompilator zmusi cię do zdefiniowania jej apply, więc nie da się pominąć przypadku.
Kiedy sięgać po enum
Używaj enuma zawsze, gdy wartość naturalnie należy do małego, stałego zbioru znanego w czasie kompilacji:
- Stany w procesie (
PENDING,SHIPPED,DELIVERED). - Kategorie, tryby lub rodzaje (
READ,WRITE,EXECUTE). - Każde miejsce, w którym kusiło cię zdefiniowanie grupy stałych
public static final int: enum daje te same nazwane wartości, a do tego bezpieczeństwo typów, czytelnytoStringi obsługę wswitchza darmo.
Nie używaj enuma dla zbioru otwartego lub ustalanego w czasie działania programu (nazwy użytkowników, lista krajów pobierana z bazy danych). Enumy są wkompilowane na stałe; jeśli zbiór zmienia się w trakcie działania programu, potrzebujesz zwykłej kolekcji.
Dalej: typy generyczne
Widzisz już, że values() zwraca Planet[], a na wcześniejszych stronach pojawiało się List<Shape>. Składnia <...> to typy generyczne (generics), sposób Javy na napisanie jednej klasy lub metody działającej dla wielu typów przy zachowaniu pełnego bezpieczeństwa typów. Dalej wyjaśnimy, jak naprawdę działają List<String> i Map<K, V> i jak pisać własne typy generyczne.
Najczęściej zadawane pytania
Czym jest enum w Javie?
enum to specjalny typ, którego wartość może być tylko jedną ze stałego, nazwanego zbioru stałych, na przykład Day.MONDAY albo Status.ACTIVE. Każda stała to pojedyncza, gotowa instancja enuma. Enumy dają bezpieczeństwo typów: metoda przyjmująca Day zawsze dostanie prawdziwy dzień, nigdy napis z literówką ani int spoza zakresu.
Czy enum w Javie może mieć pola i metody?
Tak. Enum to pełnoprawna klasa. Każdej stałej możesz przekazać argumenty konstruktora, zapisać je w polach private final i dodać metody, które z nich korzystają. Na przykład MERCURY(3.3e23, 2.4e6) przekazuje masę i promień do konstruktora enuma, a metoda surfaceGravity() może obliczać wynik na podstawie tych pól.
Czym różni się values() od valueOf() w enumie Javy?
values() zwraca tablicę wszystkich stałych enuma w kolejności deklaracji, co przydaje się do przejścia pętlą po każdej opcji. valueOf("NAME") działa odwrotnie: wyszukuje stałą, której nazwa dokładnie pasuje do napisu, i rzuca IllegalArgumentException, jeśli żadna nie pasuje. Obie metody kompilator generuje automatycznie.