"Этот подход функционально эквивалентен подходу открытого поля, за исключением того, что он более лаконичен, предоставляет механизм сериализации бесплатно и обеспечивает железную гарантию от создания нескольких экземпляров, даже перед лицом сложных атак сериализации или отражения. Хотя этот подход еще не получил широкого распространения, одноэлементный тип enum - лучший способ реализовать одноэлементный тип."
Ответ 2
В зависимости от использования существует несколько "правильных" ответов.
Начиная с Java 5, лучший способ сделать это - использовать enum:
public Object clone()throws CloneNotSupportedException{ thrownewCloneNotSupportedException("Cannot clone instance of this class"); } }
Давайте рассмотрим код. Во-первых, вы хотите, чтобы класс был final . В этом случае я использовал final ключевое слово, чтобы пользователи знали, что оно final . Тогда вам нужно сделать конструктор закрытым, чтобы пользователи не могли создавать свои собственные Foo . Выдача исключения из конструктора не позволяет пользователям использовать отражение для создания второго Foo. Затем вы создаете private static final Foo поле для хранения единственного экземпляра и public static Foo getInstance() метод для его возврата. Спецификация Java гарантирует, что конструктор вызывается только при первом использовании класса.
Когда у вас есть очень большой объект или тяжелый код построения, а также есть другие доступные статические методы или поля, которые могут быть использованы до того, как потребуется экземпляр, тогда и только тогда вам нужно использовать отложенную инициализацию.
Вы можете использовать private static class для загрузки экземпляра. Тогда код будет выглядеть следующим образом:
Поскольку строка private static final Foo INSTANCE = new Foo(); выполняется только тогда, когда фактически используется класс FooLoader, это устраняет отложенное создание экземпляра и гарантирует потокобезопасность.
Если вы также хотите иметь возможность сериализовать свой объект, вам нужно убедиться, что при десериализации не будет создана копия.
Метод readResolve() гарантирует, что будет возвращен единственный экземпляр, даже если объект был сериализован при предыдущем запуске вашей программы.
Ответ 3
Отказ от ответственности: я только что обобщил все потрясающие ответы и написал их своими словами.
При реализации одноэлементного шаблона у нас есть два варианта:
Отложенная загрузка
Ранняя загрузка
Отложенная загрузка увеличивает накладные расходы (много, если честно), поэтому используйте ее только тогда, когда у вас очень большой объект или тяжелый код построения и также есть другие доступные статические методы или поля, которые могут быть использованы до того, как потребуется экземпляр, тогда и только тогда вам нужно использовать отложенную инициализацию. В противном случае хорошим выбором будет ранняя загрузка.
Самый простой способ реализации одноэлементного шаблона - это:
publicclassFoo {
// It will be our sole hero privatestaticfinalFooINSTANCE=newFoo();
publicstatic Foo getInstance() { // Creating only when required. if (INSTANCE == null) { INSTANCE = newFoo(); } return INSTANCE; } }
Пока все хорошо, но наш герой не выживет, сражаясь в одиночку с несколькими злыми потоками, которым нужно много-много экземпляров нашего героя. Итак, давайте защитим его от злой многопоточности.:
classFoo {
privatestaticFooINSTANCE=null;
// TODO Add private shouting constructor
publicstatic Foo getInstance() { // No more tension of threads synchronized (Foo.class) { if (INSTANCE == null) { INSTANCE = newFoo(); } } return INSTANCE; } }
Но этого недостаточно, чтобы защитить нашего героя, на самом деле!!! Это лучшее, что мы можем / должны сделать, чтобы помочь нашему герою:
classFoo {
// Pay attention to volatile privatestaticvolatileFooINSTANCE=null;
Теперь мы уверены в злых потоках, но как насчет жестокой сериализации? Мы должны убедиться, что даже во время десериализации не создается новый объект:
classFooimplementsSerializable {
privatestaticfinallongserialVersionUID=1L;
privatestaticvolatileFooINSTANCE=null;
// The rest of the things are same as above
// No more fear of serialization @SuppressWarnings("unused") private Object readResolve() { return INSTANCE; } }
Метод readResolve() гарантирует, что будет возвращен единственный экземпляр, даже если объект был сериализован при предыдущем запуске нашей программы.
Наконец, мы добавили достаточную защиту от потоков и сериализации, но наш код выглядит громоздким и уродливым. Давайте переделаем нашего героя.:
publicfinalclassFooimplementsSerializable {
privatestaticfinallongserialVersionUID=1L;
// Wrapped in a inner static class so that loaded only when required privatestaticclassFooLoader {
// And no more fear of threads privatestaticfinalFooINSTANCE=newFoo(); }
Поскольку строка private static final Foo INSTANCE = new Foo(); выполняется только тогда, когда фактически используется класс FooLoader, это устраняет отложенное создание экземпляра и гарантирует потокобезопасность.
И мы зашли так далеко. Вот лучший способ добиться всего, что мы сделали, - это наилучший из возможных способов:
publicenumFoo { INSTANCE; }
Который внутренне будет обрабатываться как
publicclassFoo {
// It will be our sole hero privatestaticfinalFooINSTANCE=newFoo(); }
Этот подход функционально эквивалентен подходу открытого поля, за исключением того, что он более лаконичен, предоставляет механизм сериализации бесплатно и обеспечивает железную гарантию от создания нескольких экземпляров, даже перед лицом сложных атак сериализации или отражения. Хотя этот подход еще не получил широкого распространения, одноэлементный перечисляемый тип является лучшим способом реализации одноэлементного шаблона.
-Джошуа Блох в книге "Эффективная Java"
Теперь вы, возможно, поняли, почему ПЕРЕЧИСЛЕНИЯ считаются лучшим способом реализации одноэлементного шаблона, и спасибо за ваше терпение :)
Решение, опубликованное Стю Томпсоном, действительно в Java 5.0 и более поздних версиях. Но я бы предпочел не использовать его, потому что считаю, что оно подвержено ошибкам.
Оператор volatile легко забыть, и трудно понять, зачем он нужен. Без volatile этот код больше не был бы потокобезопасным из-за дважды проверенного антипаттерна блокировки. Подробнее об этом читайте в параграфе 16.2.4 книги Параллелизм Java на практике. Вкратце: этот шаблон (до Java 5.0 или без оператора volatile) мог возвращать ссылку на объект Bar, который (все еще) находится в некорректном состоянии.
Этот шаблон был изобретен для оптимизации производительности. Но на самом деле это больше не вызывает особой озабоченности. Следующий код отложенной инициализации быстр и, что более важно, его легче читать.