Cześć! Dzisiaj porozmawiamy o specjalnym typie danych w Javie -
Enum
(skrót od wyliczenia). Jaka jest ich cecha? Wyobraźmy sobie, że musimy wdrożyć w programie miesiące. Wydawałoby się, w czym jest problem? Musisz tylko określić, jakie właściwości ma dany miesiąc. Być może potrzebujemy przede wszystkim nazwy miesiąca i liczby w nim dni. Rozwiązanie problemu wygląda dość prosto:
public class Month {
private String name;
private int daysCount;
public Month(String name, int daysCount) {
this.name = name;
this.daysCount = daysCount;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getDaysCount() {
return daysCount;
}
public void setDaysCount(int daysCount) {
this.daysCount = daysCount;
}
@Override
public String toString() {
return "Month{" +
"name='" + name + '\'' +
", daysCount=" + daysCount +
'}';
}
}
Pełny zestaw, proszę! Mamy klasę Month
, niezbędne pola, gettery-settery, toString()
. Chyba, że equals()
trzeba hashCode()
to dodać do pełni szczęścia :) Mamy jednak problem koncepcyjny. Jak być może pamiętasz, jedną z głównych zalet OOP jest to, że ułatwia modelowanie obiektów ze świata rzeczywistego. Krzesło, samochód, planeta – wszystkie te koncepcje z życia codziennego można łatwo przedstawić w programie za pomocą abstrakcji. Problem w tym, że niektóre byty w świecie rzeczywistym mają ściśle ograniczony zakres znaczeń. W roku są tylko 4 pory roku. W muzyce jest tylko 7 nut. W kalendarzu jest tylko 12 miesięcy. Ocean ma tylko 11 przyjaciół (chociaż jest to dyskusyjne :)) Innymi słowy, zwykła klasa Java nie jest w stanie modelować tych bytów i szanować ich naturalnych ograniczeń. Nasza klasa Month
posiada wszystkie niezbędne pola. Ale jeśli użyje go inny programista, nikt mu nie przeszkodzi w stworzeniu zupełnie szalonych obiektów:
public class Main {
Month month1 = new Month("lolkek", 322);
Month month2 = new Month("yahoooooooooooo", 12345);
}
Jeśli pojawi się to w programie, znalezienie winowajcy nie będzie łatwe! Z jednej strony programista tworzący obiekty mógł zrozumieć, że klasa Month
oznacza „miesiąc w roku” i nie pisać takich bzdur. Z drugiej strony po prostu korzystał z możliwości, jakie zapewnił mu projektant klas. Czy mogę przypisać dowolne imiona i liczbę dni? On to wyznaczył. Co zrobić w takiej sytuacji? Przed wydaniem wersji 1.5 języka Java programiści musieli, szczerze mówiąc, wyjść z tego :) W tamtych czasach stworzyli następujące konstrukcje:
public class Month {
private String name;
private int daysCount;
private Month(String name, int daysCount) {
this.name = name;
this.daysCount = daysCount;
}
public static Month JANUARY = new Month("Январь", 31);
public static Month FEBRUARY = new Month("Февраль", 28);
public static Month MARCH = new Month("Март", 31);
@Override
public String toString() {
return "Month{" +
"name='" + name + '\'' +
", daysCount=" + daysCount +
'}';
}
}
Tutaj uprościliśmy liczbę miesięcy do trzech zamiast dwunastu, aby przykład był krótszy. Takie projekty umożliwiły rozwiązanie problemu. Możliwości tworzonych obiektów zostały ograniczone przez prywatnego konstruktora:
private Month(String name, int daysCount) {
this.name = name;
this.daysCount = daysCount;
}
Programiści korzystający z tej klasy nie mogli po prostu tworzyć plików Month
. Byli zmuszeni użyć tych końcowych obiektów statycznych, które dostarczył twórca klasy. Wyglądało to mniej więcej tak:
public class Main {
public static void main(String[] args) {
Month january = Month.JANUARY;
System.out.println(january);
}
}
Jednak programiści Java zauważyli istniejący problem. Oczywiście wspaniale, że programistom udało się wymyślić rozwiązanie korzystając z narzędzi dostępnych w tym języku, jednak nie wygląda to na takie proste! Potrzebne było rozwiązanie oczywiste, dostępne nawet dla początkujących. W ten sposób pojawił się w Javie Enum
. Zasadniczo Enum
jest to klasa Java udostępniająca ograniczony zestaw obiektów wartości. Oto jak to wygląda:
public enum Month {
JANUARY,
FEBRUARY,
MARCH
}
W definicji wskazaliśmy, że Enum
jest to klasa Java, ale czy to rzeczywiście prawda? Tak i możemy to nawet sprawdzić. Spróbuj na przykład odziedziczyć naszą enum Month
z innej klasy:
public abstract class AbstractMonth {
}
//ошибка! No extends clause allowed to enum
public enum Month extends AbstractMonth {
JANUARY,
FEBRUARY,
MARCH
}
Dlaczego to się dzieje? Kiedy piszemy do programu:
public enum Month
Kompilator konwertuje to polecenie na kod taki jak ten:
public Class Month extends Enum
Jak już wiesz, dziedziczenie wielokrotne nie jest dozwolone w Javie. Dlatego AbstractMonth
nie mogliśmy dziedziczyć. Jak można Enum
wykorzystać ten nowy projekt? A czym różni się od starego projektu z static final
polami? Cóż, na przykład stary projekt nie pozwalał nam na użycie własnego zestawu wartości w switch
wyrażeniach. Wyobraźmy sobie, że chcemy stworzyć program, który przypomni nam, jakie święta obchodzone są w tym miesiącu:
public class HolidayReminder {
public void printHolidays(Month month) {
switch (month) {
//błąd!
case JANUARY:
}
}
}
Tutaj, jak widać, kompilator zgłasza błąd. Ale po pojawieniu się Java 1.5 enum
wszystko stało się znacznie prostsze:
public enum Month {
JANUARY,
FEBRUARY,
MARCH
}
public class HolidayReminder {
public void printHolidays(Month month) {
switch (month) {
case JANUARY:
System.out.println("7 января будет Рождество!");
break;
case FEBRUARY:
System.out.println("В феврале празднуется День Защитника Отечества - 23 февраля!");
break;
case MARCH:
System.out.println("В марте отмечается Всемирный Женский День - 8 марта!");
break;
}
}
}
public class Main {
public static void main(String[] args) {
HolidayReminder reminder = new HolidayReminder();
reminder.printHolidays(Month.JANUARY);
}
}
Wyjście konsoli:
7 января будет Рождество!
Uwaga: dostęp do obiektów Enum
pozostaje statyczny, tak jak przed wersją Java 1.5. Nie musimy tworzyć obiektu, Month
aby uzyskać dostęp do miesięcy. Pracując z wyliczeniami, bardzo ważne jest, aby nie zapominać, że Enum
jest to pełnoprawna klasa. Oznacza to, że w razie potrzeby można w nim zdefiniować konstruktory i metody. Przykładowo w poprzednim fragmencie kodu po prostu podaliśmy wartości STYCZEŃ, LUTY, MARZEC. Możemy jednak rozszerzyć nasze enum Month
w ten sposób:
public enum Month {
JANUARY("Январь", 31),
FEBRUARY("Февраль", 28),
MARCH("Март", 31),
APRIL("Апрель", 30),
MAY("Май", 31),
JUNE("Июнь", 30),
JULY("Июль", 31),
AUGUST("Август", 31),
SEPTEMBER("Сентябрь", 30),
OCTOBER("Октябрь", 31),
NOVEMBER("Ноябрь", 30),
DECEMBER("Декабрь", 31);
private String name;
private int daysCount;
Month(String name, int daysCount) {
this.name = name;
this.daysCount = daysCount;
}
public static Month[] getWinterMonths() {
return new Month[]{DECEMBER, JANUARY, FEBRUARY};
}
public static Month[] getSummerMonths() {
return new Month[]{JUNE, JULY, AUGUST};
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getDaysCount() {
return daysCount;
}
public void setDaysCount(int daysCount) {
this.daysCount = daysCount;
}
@Override
public String toString() {
return "Month{" +
"name='" + name + '\'' +
", daysCount=" + daysCount +
'}';
}
}
Tutaj dodaliśmy do naszego 2 pola enum
- nazwę miesiąca i liczbę dni, konstruktor korzystający z tych pól, gettery-settery, metodę toString()
, a także 2 metody statyczne. Jak widać, nie było z tym żadnych problemów: jak powiedzieliśmy wcześniej, enum
jest to pełnoprawna klasa:
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
System.out.println(Arrays.toString(Month.getSummerMonths()));
}
}
Wyjście konsoli:
[Month{name='Июнь', daysCount=30}, Month{name='Июль', daysCount=31}, Month{name='Август', daysCount=31}]
Na koniec chcę Ci polecić jedną niezwykle przydatną książkę o Javie, a mianowicie „Efektywna Java” Joshuy Blocha . Autor jest jednym z twórców Javy, dlatego z całą pewnością można zaufać jego radom dotyczącym prawidłowego i kompetentnego posługiwania się narzędziami językowymi :) W nawiązaniu do naszego wykładu radzę zwrócić szczególną uwagę na rozdział książki poświęcony enum
. Życzymy produktywnej lektury! :)
GO TO FULL VERSION