Зачем использовать какую-либо функцию языка программирования? Причина, по которой у нас есть языки,
- Программисты могут эффективно и правильно выражать алгоритмы в форме, которую могут использовать компьютеры.
- Сопровождающие понимают алгоритмы, написанные другими, и правильно вносят изменения.
Перечисления улучшают как вероятность правильности, так и читабельности без написания большого количества шаблонов. Если вы хотите написать шаблонный шаблон, вы можете «смоделировать» перечисления:
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();
}
Теперь вы можете написать:
Color trafficLightColor = Color.RED;
Приведенный выше шаблон имеет тот же эффект, что и
public enum Color { RED, AMBER, GREEN };
Оба предоставляют одинаковый уровень проверки справки от компилятора. Boilerplate просто больше печатать. Но сохранение большого количества печати делает программиста более эффективным (см. 1), так что это полезная функция.
Это стоит по крайней мере еще по одной причине:
Смена операторов
Одна вещь, которую static final
перечисленное выше моделирование перечисления не дает вам, это хорошие switch
случаи. Для перечислимых типов переключатель Java использует тип своей переменной, чтобы вывести область действия перечислений, поэтому для enum Color
вышесказанного вам просто нужно сказать:
Color color = ... ;
switch (color) {
case RED:
...
break;
}
Обратите внимание, что это не Color.RED
в случаях. Если вы не используете enum, единственный способ использовать именованные величины с switch
чем-то вроде:
public Class Color {
public static final int RED = 0;
public static final int AMBER = 1;
public static final int GREEN = 2;
}
Но теперь переменная для хранения цвета должна иметь тип int
. Хорошая проверка перечисления и static final
моделирования компилятором исчезла. Не счастлив.
Компромиссом является использование скалярного члена в симуляции:
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);
}
Сейчас же:
Color color = ... ;
switch (color.tag) {
case Color.RED_TAG:
...
break;
}
Но обратите внимание, еще более шаблонный!
Использование enum в качестве синглтона
Из вышеприведенного примера видно, почему перечисление предоставляет способ реализации синглтона. Вместо того чтобы писать:
public class SingletonClass {
public static final void INSTANCE = new SingletonClass();
private SingletonClass() {}
// all the methods and instance data for the class here
}
а затем доступ к нему с
SingletonClass.INSTANCE
мы можем просто сказать
public enum SingletonClass {
INSTANCE;
// all the methods and instance data for the class here
}
что дает нам то же самое. Мы можем уйти с этим , потому что Java перечислений будут реализованы в виде полных классов только с небольшим синтаксическим сахаром посыпают сверху. Это опять-таки менее шаблонно, но не очевидно, если идиома вам не знакома. Мне также не нравится тот факт, что вы получаете различные функции перечисления, даже если они не имеют особого смысла для синглтона: ord
и values
т. Д. (На самом деле есть более Color extends Integer
сложная симуляция, где это будет работать с switch, но это настолько сложно, что это даже больше ясно показывает, почему enum
это лучшая идея.)
Поток безопасности
Потоковая безопасность является потенциальной проблемой только тогда, когда синглтоны создаются лениво без блокировки.
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
}
Если много потоков вызывают getInstance
одновременно, в то время INSTANCE
как все еще нуль, может быть создано любое количество экземпляров. Это плохо. Единственное решение - добавить synchronized
доступ для защиты переменной INSTANCE
.
Однако приведенный static final
выше код не имеет этой проблемы. Он создает экземпляр с нетерпением во время загрузки класса. Загрузка классов синхронизирована.
enum
Одноточечно эффективно ленивая , потому что он никогда не инициализируется до первого использования. Инициализация Java также синхронизирована, поэтому несколько потоков не могут инициализировать более одного экземпляра INSTANCE
. Вы получаете лениво инициализированный синглтон с очень небольшим кодом. Единственный минус - довольно туманный синтаксис. Вам нужно знать идиому или полностью понимать, как работают загрузка и инициализация класса, чтобы знать, что происходит.