Cześć! Dzisiaj porozmawiamy o jednym ze specjalnych typów danych w Javie:
Enum
(skrót od „enumeration”). Co czyni go wyjątkowym? Wyobraźmy sobie, czego potrzebujemy, aby zaimplementować „miesiące” w programie. Nie wydaje się problematyczne, prawda? Musimy tylko określić, jakie właściwości ma dany miesiąc. Być może najpierw potrzebujemy nazwy miesiąca i liczby dni w nim zawartych. Rozwiązanie 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 +
'}';
}
}
Cały szaban! Mamy Month
klasę, wymagane pola, pobierające/ustawiające i toString()
. O ile oczywiście nie musimy dodać equals()
ihashCode()
osiągnąć pełnię szczęścia :) Ale tutaj mamy problem pojęciowy. Jak zapewne pamiętasz, jedną z głównych zalet OOP jest to, że ułatwia modelowanie obiektów z rzeczywistego świata. Krzesło, samochód, planeta — wszystkie te pojęcia ze zwykłego życia można łatwo przedstawić w programie za pomocą abstrakcji. Problem polega na tym, że niektóre byty w świecie rzeczywistym mają ściśle ograniczony zakres wartości. W roku są tylko 4 pory roku. W oktawie jest tylko 8 nut. Kalendarz ma tylko 12 miesięcy. A Danny Ocean z Ocean's 11 ma tylko 11 przyjaciół (choć to nie ma znaczenia :)) Liczy się to, że zwykła klasa Javy nie jest w stanie modelować tych bytów i narzucać ich naturalnych ograniczeń. NaszMonth
class ma wszystkie wymagane pola. Ale jeśli użyje go inny programista, nikt nie może go powstrzymać przed tworzeniem całkowicie szalonych obiektów:
public class Main {
Month month1 = new Month("lolkek", 322);
Month month2 = new Month("yahoooooooooooo", 12345);
}
Jeśli pojawi się to w naszym kodzie, nie będzie łatwo znaleźć winowajcę! Z jednej strony programista tworzący obiekty mógłby sobie uświadomić, że Month
klasa oznacza „miesiąc w roku” i nie pisać takich bzdur. Z drugiej strony programista korzysta tylko z możliwości, które zapewnia projektant klasy. Czy można nadawać dowolne nazwy i numery dni? Dokładnie to mamy. Co w takim razie powinniśmy zrobić w tej sytuacji? Szczerze mówiąc, zanim wypuszczono Javę 1.5, programiści musieli wykazać się kreatywnością :) W tamtych czasach tworzyli takie struktury:
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("January", 31);
public static Month FEBRUARY = new Month("February", 28);
public static Month MARCH = new Month("March", 31);
@Override
public String toString() {
return "Month{" +
"name='" + name + '\'' +
", daysCount=" + daysCount +
'}';
}
}
Tutaj skróciliśmy liczbę miesięcy z dwunastu do trzech, aby skrócić przykład. Takie projekty umożliwiły rozwiązanie problemu. Możliwość tworzenia obiektów została ograniczona do 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ć Month
obiektów. Musieli użyć ostatecznych obiektów statycznych dostarczonych przez twórcę klasy. Na przykład tak:
public class Main {
public static void main(String[] args) {
Month january = Month.JANUARY;
System.out.println(january);
}
}
Jednak programiści Java zwrócili uwagę na istniejący problem. Oczywiście to wspaniale, że programiści byli w stanie wymyślić rozwiązanie przy użyciu narzędzi dostępnych w języku, ale nie wygląda to na takie proste! Potrzebne było oczywiste rozwiązanie, nawet dla nowicjuszy. I tak Enum
pojawił się w Javie. Zasadniczo Enum
jest to klasa Java, która zapewnia ograniczony zestaw wartości obiektów. Oto jak to wygląda:
public enum Month {
JANUARY,
FEBRUARY,
MARCH
}
W definicji wskazaliśmy, że Enum
jest to klasa Java, ale czy to naprawdę prawda? Tak, a nawet możemy to zweryfikować. Na przykład spróbuj sprawić, by nasze Month
wyliczenie odziedziczyło inną klasę:
public abstract class AbstractMonth {
}
// Error! The extends clause cannot be used with an enum
public enum Month extends AbstractMonth {
JANUARY,
FEBRUARY,
MARCH
}
Dlaczego tak się dzieje? Kiedy piszemy:
public enum Month
kompilator konwertuje tę instrukcję na następujący kod:
public Class Month extends Enum
Jak już wiesz, Java nie obsługuje wielokrotnego dziedziczenia. Dlatego nie możemy dziedziczyć AbstractMonth
. Enum
Jak można wykorzystać ten nowy konstrukt, ,? I czym różni się od starego konstruktu z static final
polami? Cóż, na przykład stara konstrukcja nie pozwalała nam używać własnego zestawu wartości w switch
instrukcjach. Wyobraź sobie, że chcemy stworzyć program, który będzie nam przypominał o świętach obchodzonych w każdym miesiącu:
public class HolidayReminder {
public void printHolidays(Month month) {
switch (month) {
// Error!
case JANUARY:
}
}
}
Jak widać, kompilator wyrzuca tutaj błąd. Ale kiedy enum
pojawił się w Javie 1.5, 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("New Year's Day is January 1st!");
break;
case FEBRUARY:
System.out.println("Valentine's Day is February 14th!");
break;
case MARCH:
System.out.println("Saint Patrick's Day is March 17th!");
break;
}
}
}
public class Main {
public static void main(String[] args) {
HolidayReminder reminder = new HolidayReminder();
reminder.printHolidays(Month.JANUARY);
}
}
Wyjście konsoli:
New Year's Day is January 1st!
Należy zauważyć, że dostęp do Enum
obiektów pozostał statyczny, tak jak przed Javą 1.5. Nie musimy tworzyć Month
obiektu, aby uzyskać dostęp do miesięcy. Podczas pracy 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. Na przykład w poprzednim fragmencie kodu po prostu określiliśmy wartości: JANUARY, FEBRUARY, MARCH. Możemy jednak rozszerzyć nasze Month
wyliczenie w następujący sposób:
public enum Month {
JANUARY("January", 31),
FEBRUARY("February", 28),
MARCH("March", 31),
APRIL("April", 30),
MAY("May", 31),
JUNE("June", 30),
JULY("July", 31),
AUGUST("August", 31),
SEPTEMBER("September", 30),
OCTOBER("October", 31),
NOVEMBER("November", 30),
DECEMBER("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 podaliśmy nasze enum
2 pola (nazwę miesiąca i liczbę dni), konstruktor korzystający z tych pól, getter/settery, metodę toString()
i 2 metody statyczne. Jak widać, nie było z tym żadnych problemów. Ponownie, Enum
naprawdę jest pełnoprawną klasą:
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='June', daysCount=30}, Month{name='July', daysCount=31}, Month{name='August', daysCount=31}]
Na koniec chcę polecić niezwykle przydatną książkę o Javie, a mianowicie „Efektywna Java” autorstwa Joshua Blocha . Autor jest jednym z twórców Javy, więc zdecydowanie możesz zaufać jego radom, jak poprawnie i kompetentnie korzystać z narzędzi języka :) W odniesieniu do naszej lekcji, polecam zwrócić szczególną uwagę na rozdział książki pt Enum
. Miłej lektury! :)