Зачем использовать какую-либо функцию языка программирования? Причина, по которой у нас есть языки, -
- Программисты эффективно и правильно выражают алгоритмы в форме, которую могут использовать компьютеры.
- Сопровождающие понимают алгоритмы, написанные другими, и правильно вносят изменения.
Перечисления улучшают как вероятность правильности, так и читабельности без написания большого количества шаблонов. Если вы хотите написать шаблонный шаблон, вы можете «смоделировать» перечисления:
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;
}
Но обратите внимание, еще больше шаблонов!
Использование перечисления в качестве единственного числа
Из приведенного выше шаблона видно, почему перечисление предоставляет способ реализации синглтона. Вместо записи:
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
будет работать с переключателем , но это настолько сложно, что еще более ясно показывает, почему 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
. Вы получаете лениво инициализированный синглтон с очень небольшим кодом. Единственный минус - довольно туманный синтаксис. Вам нужно знать идиому или полностью понимать, как работают загрузка и инициализация класса, чтобы знать, что происходит.