Традиционно синглтон обычно реализуется как
public class Foo1
{
private static final Foo1 INSTANCE = new Foo1();
public static Foo1 getInstance(){ return INSTANCE; }
private Foo1(){}
public void doo(){ ... }
}
С помощью перечисления Java мы можем реализовать синглтон как
public enum Foo2
{
INSTANCE;
public void doo(){ ... }
}
Как бы ни была хороша вторая версия, есть ли у нее недостатки?
(Я подумал об этом, и я отвечу на свой вопрос; надеюсь, у вас есть лучшие ответы)
Ответы:
Некоторые проблемы с синглетонами enum:
Приверженность стратегии реализации
Как правило, «синглтон» относится к стратегии реализации, а не к спецификации API. Очень редко
Foo1.getInstance()
публично заявляют, что он всегда будет возвращать один и тот же экземпляр. При необходимости реализацияFoo1.getInstance()
может развиваться, например, для возврата одного экземпляра на поток.С
Foo2.INSTANCE
мы публично заявляем , что данный экземпляр экземпляр, и нет никаких шансов , чтобы изменить это. Стратегия реализации, состоящая в том, чтобы иметь единственный экземпляр, является открытой и преданной.Эта проблема не наносит вред. Например,
Foo2.INSTANCE.doo()
может полагаться на локальный вспомогательный объект потока, чтобы эффективно иметь экземпляр для каждого потока.Расширение класса Enum
Foo2
расширяет супер классEnum<Foo2>
. Мы обычно хотим избегать супер-классов; особенно в этом случае, вынужденный суперклассFoo2
не имеет ничего общего с тем, чтоFoo2
должно быть. Это загрязнение иерархии типов нашего приложения. Если мы действительно хотим суперкласс, обычно это класс приложения, но мы не можем,Foo2
суперкласс исправлен.Foo2
наследует некоторые забавные методы экземпляраname(), cardinal(), compareTo(Foo2)
, которые просто сбивают с толкуFoo2
пользователей России.Foo2
не может иметь свой собственныйname()
метод, даже если этот метод желателен вFoo2
интерфейсе.Foo2
также содержит несколько забавных статических методовкоторый кажется бессмысленным для пользователей. У синглтона обычно не должно быть пульсирующих статических методов в любом случае (кроме
getInstance()
)Сериализуемость
Синглтонам очень свойственно быть с состоянием. Эти синглтоны обычно не должны быть сериализуемыми. Я не могу вспомнить ни одного реалистичного примера, когда имеет смысл переносить синглтон с состоянием с одной виртуальной машины на другую; «Синглтон» означает «уникальный внутри ВМ», а не «уникальный во вселенной».
Если сериализация действительно имеет смысл для синглтона с состоянием, то синглтон должен явно и точно указать, что значит десериализовать синглтон в другой виртуальной машине, где синглтон того же типа может уже существовать.
Foo2
автоматически фиксирует упрощенную стратегию сериализации / десериализации. Это просто несчастный случай, ожидающий случиться. Если у нас есть дерево данных, концептуально ссылающееся на переменную состоянияFoo2
в VM1 в момент времени t1, посредством сериализации / десериализации значение становится другим значением - значением той же переменнойFoo2
в VM2 в момент времени t2, что создает трудно обнаруживаемую ошибку. Эта ошибка не произойдет с не сериализуемымFoo1
молча.Ограничения кодирования
Есть вещи, которые можно делать в обычных классах, но запрещать в
enum
классах. Например, доступ к статическому полю в конструкторе. Программист должен быть более осторожным, поскольку он работает в специальном классе.Вывод
Используя перечисление enum, мы сохраняем 2 строки кода; но цена слишком высока, мы должны нести все мешки и ограничения перечислений, мы непреднамеренно наследуем «особенности» перечислений, которые имеют непредвиденные последствия. Единственное заявленное преимущество - автоматическая сериализуемость - оказывается недостатком.
источник
Constructor<?> c=EnumType.class.getDeclaredConstructors()[0]; c.setAccessible(true); EnumType f=(EnumType)MethodHandles.lookup().unreflectConstructor(c).invokeExact("BAR", 1);
например, действительно хороший пример :;Constructor<?> c=Thread.State.class.getDeclaredConstructors()[0]; c.setAccessible(true); Thread.State f=(Thread.State)MethodHandles.lookup().unreflectConstructor(c).invokeExact("RUNNING_BACKWARD", -1);
^), протестирован под Java 7 и Java 8…Экземпляр enum зависит от загрузчика классов. т. е. если у вас есть загрузчик второго класса, у которого загрузчик первого класса не является родительским, загружающим один и тот же класс enum, вы можете получить несколько экземпляров в памяти.
Пример кода
Создайте следующее перечисление и поместите его файл .class в jar-файл. (конечно, фляга будет иметь правильную структуру пакета / папки)
Теперь запустите этот тест, убедившись, что в пути к классам нет копий перечисленного выше перечисления:
И теперь у нас есть два объекта, представляющих одно и то же значение перечисления.
Я согласен, что это редкий и надуманный угловой случай, и почти всегда enum может использоваться для java-синглтона. Я делаю это сам. Но вопрос, который задают о потенциальных недостатках, и эта предостережение стоит знать.
источник
Шаблон enum не может быть использован для любого класса, который может вызвать исключение в конструкторе. Если это требуется, используйте фабрику:
источник