Dlaczego warto korzystać z funkcji języka programowania? Powód, dla którego w ogóle mamy języki
- Programiści do wydajnego i prawidłowego wyrażania algorytmów w formie, w której mogą używać komputery.
- Opiekunowie, aby zrozumieć algorytmy, które inni napisali i poprawnie wprowadzają zmiany.
Wyliczenia zwiększają zarówno prawdopodobieństwo poprawności, jak i czytelności, bez pisania dużej liczby szablonów. Jeśli chcesz napisać bojler, możesz „symulować” wyliczenia:
public class Color {
private Color() {} // Prevent others from making colors.
public static final Color RED = new Color();
public static final Color AMBER = new Color();
public static final Color GREEN = new Color();
}
Teraz możesz napisać:
Color trafficLightColor = Color.RED;
Powyższa płyta ma podobny efekt jak
public enum Color { RED, AMBER, GREEN };
Oba zapewniają ten sam poziom sprawdzania pomocy kompilatora. Boilerplate to po prostu więcej pisania. Ale zaoszczędzenie dużej liczby pisania sprawia, że programista jest bardziej wydajny (patrz 1), więc jest to przydatna funkcja.
Warto jest z co najmniej jeszcze jednego powodu:
Instrukcje przełączania
Jedną z rzeczy, których nie daje static final
powyższa symulacja wyliczania , są ładne przypadki. W przypadku typów wyliczeniowych przełącznik Java używa typu swojej zmiennej, aby wywnioskować zakres przypadków wyliczania, więc w powyższym przypadku wystarczy powiedzieć:switch
enum Color
Color color = ... ;
switch (color) {
case RED:
...
break;
}
Pamiętaj, że nie ma tego Color.RED
w przypadkach. Jeśli nie używasz wyliczenia, jedynym sposobem użycia nazwanych ilości switch
jest coś takiego:
public Class Color {
public static final int RED = 0;
public static final int AMBER = 1;
public static final int GREEN = 2;
}
Ale teraz zmienna do przechowywania koloru musi mieć typ int
. Ładne sprawdzanie wyliczenia i static final
symulacji przez kompilator zniknęło. Nieszczęśliwy.
Kompromisem jest użycie elementu symulacji o wartości skalarnej:
public class Color {
public static final int RED_TAG = 1;
public static final int AMBER_TAG = 2;
public static final int GREEN_TAG = 3;
public final int tag;
private Color(int tag) { this.tag = tag; }
public static final Color RED = new Color(RED_TAG);
public static final Color AMBER = new Color(AMBER_TAG);
public static final Color GREEN = new Color(GREEN_TAG);
}
Teraz:
Color color = ... ;
switch (color.tag) {
case Color.RED_TAG:
...
break;
}
Ale uwaga, jeszcze więcej płyt grzewczych!
Używanie wyliczenia jako singletonu
Z powyższej płyty kotłowej możesz zobaczyć, dlaczego enum zapewnia sposób na wdrożenie singletonu. Zamiast pisać:
public class SingletonClass {
public static final void INSTANCE = new SingletonClass();
private SingletonClass() {}
// all the methods and instance data for the class here
}
a następnie dostęp do niego za pomocą
SingletonClass.INSTANCE
możemy po prostu powiedzieć
public enum SingletonClass {
INSTANCE;
// all the methods and instance data for the class here
}
co daje nam to samo. Możemy temu zaradzić, ponieważ wyliczenia Java są zaimplementowane jako pełne klasy z niewielką ilością cukru syntaktycznego posypanego ponad górną krawędź. To jest znowu mniej kotłów, ale nie jest to oczywiste, chyba że idiom jest ci znany. Nie podoba mi się również fakt, że otrzymujesz różne funkcje wyliczania, nawet jeśli nie mają one większego sensu dla singletonu: ord
i values
itp. (W rzeczywistości jest trudniejsza symulacja, w której Color extends Integer
będzie działać z przełącznikiem, ale jest tak trudna, że jest jeszcze bardziej wyraźnie pokazuje, dlaczego enum
jest lepszy pomysł).
Bezpieczeństwo wątków
Bezpieczeństwo wątków jest potencjalnym problemem tylko wtedy, gdy singletony są tworzone leniwie, bez blokowania.
public class SingletonClass {
private static SingletonClass INSTANCE;
private SingletonClass() {}
public SingletonClass getInstance() {
if (INSTANCE == null) INSTANCE = new SingletonClass();
return INSTANCE;
}
// all the methods and instance data for the class here
}
Jeśli wiele wątków wywołuje getInstance
jednocześnie, gdy INSTANCE
wciąż jest pusta, można utworzyć dowolną liczbę wystąpień. To jest złe. Jedynym rozwiązaniem jest dodanie synchronized
dostępu w celu ochrony zmiennej INSTANCE
.
Jednak static final
powyższy kod nie ma tego problemu. Tworzy instancję chętnie podczas ładowania klasy. Ładowanie klasy jest zsynchronizowane.
enum
Singleton jest efektywnie leniwy, ponieważ nie jest inicjowany dopiero pierwszego użycia. Inicjalizacja Java jest również synchronizowana, więc wiele wątków nie może zainicjować więcej niż jednego wystąpienia INSTANCE
. Otrzymujesz leniwie zainicjowany singleton z bardzo małą ilością kodu. Jedynym minusem jest raczej niejasna składnia. Musisz znać ten idiom lub dokładnie zrozumieć, jak działają ładowanie i inicjowanie klas, aby wiedzieć, co się dzieje.