Недавно мы с тобой разбирались, что такое Singleton, как его реализовать на Java и для чего он нужен. Но что если я тебе расскажу, что у Java уже есть свой синглтон из коробки? Интересно? Тогда давай разбираться.

Вероятно, ты уже знаешь о классе enum. У него есть особенность, о которой тебе стоит знать. Дело в том, что enum является реализацией синглтона. Такой вариант почти аналогичен подходу с public полем.

Singleton as enum:

public enum Device {
    PRINTER
}

Public variable singleton:

public class Printer {
    public static final Printer PRINTER = new Printer();
    private Printer() {
    }
//…
}

В случае с публичным полем вариант с enum более компактен — не нужно писать свою реализацию. И что самое важное — у енамов нет проблем с сериализацией.

Сериализация здесь работает не так, как для для обычных объектов: сериализируется только значение имени енама. При десериализации метод используется с десериализованным именем, чтобы получить экземпляр. Также enum позволяет защитить себя от рефлексивных атак.

Больше о Reflection ты узнаешь из лекций второго модуля, в теме Reflection API.

Java представляет запрет на инстанциирование енамов, и это закодировано в реализации метода newInstance класса Constructor, который часто вызывают при создании объектов через рефлексию.

Часть кода из Constructor.newInstance для создания enum:

if ((clazz.getModifiers() & Modifier.ENUM) != 0)
    throw new IllegalArgumentException("Cannot reflectively create enum objects");

Из минусов создания Singleton через enum стоит назвать:

  • Отсутствие ленивой инициализации, так как объект создается сразу, и нельзя сделать отложенную инициализацию.

  • Невозможно расширять другие классы. То есть использовать enum как Singleton в случаях, когда нужно наследоваться от другого класса, не получится. В таких случаях нужно обращаться к уже знакомым нам вариантам реализации через статический метод или публичную переменную.

  • Используя enum как синглтон, ты можешь испольовать только одно поле в enum классе.

public enum Device extends Electricity {
    PRINTER
}

Такой код выдаст нам ошибку компиляции:

No extends clause allowed for enum

Но если нам нужно реализовать интерфейс, нет проблем: enum-ы могут имплементировать интерфейсы:

public enum Device implements Electricity {
    PRINTER
}

Если тебе не нужно использовать наследование, лучше использовать реализацию Singleton через enum. Так советуем делать не только мы, но и сам Джошуа Блох.

Использование такой реализации дает тебе удобство, компактность, сериализацию из коробки, защиту от рефлексивных атак и уникальность, а это то, что нужно для хорошего Синглтона!