Implementowanie Singletona z Enum (w Javie)

178

Wyczytałem, że jest możliwe zaimplementowanie Singletonw Javie przy użyciu Enumnp .:

public enum MySingleton {
     INSTANCE;   
}

Ale jak to działa? W szczególności Objectnależy utworzyć instancję. Tutaj, jak jest MySingletontworzony? Kto to robi new MySingleton()?

Anand
źródło
24
Kto robi nowy MySingleton () JVM to.
Sotirios Delimanolis
37
INSTANCEjest taki sam jak public static final MySingleton INSTANCE = new MySingleton();.
Pshemo
6
ENUM - gwarantowany singleton.
To nie błąd
Przeczytaj dzone.com/articles/java-singletons-using-enum , dlaczego używać enum, a nie innych metod. Krótko mówiąc: problemy zaczynają się podczas używania serializacji i odbicia.
Miyagi

Odpowiedzi:

203

To,

public enum MySingleton {
  INSTANCE;   
}

ma niejawny pusty konstruktor. Zamiast tego wyraź to wyraźnie,

public enum MySingleton {
    INSTANCE;
    private MySingleton() {
        System.out.println("Here");
    }
}

Jeśli następnie dodałeś kolejną klasę za pomocą main()metody takiej jak

public static void main(String[] args) {
    System.out.println(MySingleton.INSTANCE);
}

Zobaczyłbyś

Here
INSTANCE

enumpola są stałymi czasu kompilacji, ale są instancjami swojego enumtypu. Są one konstruowane, gdy typ wyliczenia jest przywoływany po raz pierwszy .

Elliott Frisch
źródło
13
Powinieneś dodać, że domyślnie wyliczenia mają niejawny prywatny konstruktor i że jawne dodawanie prywatnego konstruktora nie jest potrzebne, chyba że faktycznie masz kod, który musisz uruchomić w tym konstruktorze
Nimrod Dayan
każde pole wyliczenia tworzy instancję tylko raz, więc nie ma potrzeby tworzenia prywatnego konstruktora.
Pravat Panda
2
public enum MySingleton {INSTANCE, INSTANCE1; }, a następnie System.out.println (MySingleton.INSTANCE.hashCode ()); System.out.println (MySingleton.INSTANCE1.hashCode ()); wypisuje różne hashcodes. Czy to oznacza, że ​​powstają dwa obiekty MySingleton?
mile scott
@scottmiles Yes. Ponieważ masz dwie instancje. Singleton z definicji ma jeden.
Elliott Frisch
privateModyfikator nie ma żadnego znaczenia dla enumkonstruktora i jest całkowicie zbędne.
Dmitri Nesteruk
76

enumTyp jest specjalnym typem class.

Twoja enumwola zostanie faktycznie skompilowana do czegoś takiego jak

public final class MySingleton {
    public final static MySingleton INSTANCE = new MySingleton();
    private MySingleton(){} 
}

Gdy kod uzyskuje dostęp po raz pierwszy INSTANCE, klasa MySingletonzostanie załadowana i zainicjowana przez maszynę JVM. Ten proces inicjalizuje staticpole powyżej jeden raz (leniwie).

Sotirios Delimanolis
źródło
Czy ta klasa ma również zawierać konstruktora prywatnego ()? Myślę, że tak byłoby
Yasir Shabbir Choudhary
public enum MySingleton { INSTANCE,INSTANCE1; }a następnie System.out.println(MySingleton.INSTANCE.hashCode()); System.out.println(MySingleton.INSTANCE1.hashCode());drukuje różne hashcodes. Czy to oznacza, że ​​powstają dwa obiekty MySingleton?
mile scott
2
@scottmiles Tak, to tylko dwie enumstałe różnicowe .
Sotirios Delimanolis
@SotiriosDelimanolis, czy to podejście z wątkiem enum jest bezpieczne?
abg
63

W tej książce o najlepszych praktykach dotyczących języka Java autorstwa Joshua Blocha można znaleźć wyjaśnienie, dlaczego należy wymusić właściwość Singleton za pomocą prywatnego konstruktora lub typu Enum. Rozdział jest dość długi, więc podsumowując:

Skonfigurowanie klasy jako singletona może utrudnić testowanie jej klientów, ponieważ nie jest możliwe zastąpienie pojedynczej implementacji fałszywej, chyba że implementuje interfejs, który służy jako jego typ. Zalecanym podejściem jest implementacja Singletonów poprzez proste utworzenie typu wyliczenia z jednym elementem:

// Enum singleton - the preferred approach
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() { ... }
}

To podejście jest funkcjonalnie równoważne z podejściem w terenie publicznym, z tym wyjątkiem, że jest bardziej zwięzłe, zapewnia maszynę do serializacji za darmo i zapewnia żelazną gwarancję przed wielokrotnymi instancjami, nawet w obliczu wyrafinowanych ataków serializacji lub odbicia.

Chociaż to podejście nie zostało jeszcze powszechnie przyjęte, jednoelementowy typ wyliczeniowy jest najlepszym sposobem na zaimplementowanie pojedynczego elementu.

ekostadinov
źródło
Myślę, że aby uczynić singletona możliwym do przetestowania, można użyć wzorca strategii i pozwolić, aby wszystkie akcje singletona przechodziły przez strategię. Możesz więc mieć jedną strategię „produkcyjną” i jedną strategię „testową” ...
Erk,
9

Podobnie jak wszystkie instancje wyliczenia, Java tworzy instancję każdego obiektu, gdy klasa jest ładowana, z pewną gwarancją, że jest ona tworzona dokładnie raz na JVM . Potraktuj INSTANCEdeklarację jako publiczne statyczne pole końcowe: Java utworzy instancję obiektu przy pierwszym odwołaniu do klasy.

Instancje są tworzone podczas statycznej inicjalizacji, która jest zdefiniowana w specyfikacji języka Java, sekcja 12.4 .

Co jest warte, Joshua Bloch szczegółowo opisuje ten wzorzec jako punkt 3 w Effective Java Second Edition .

Jeff Bowman
źródło
6

Ponieważ Singleton Pattern polega na posiadaniu prywatnego konstruktora i wywołaniu jakiejś metody do sterowania instancjami (jak niektóre getInstance), w Enums mamy już niejawny prywatny konstruktor.

Nie wiem dokładnie, w jaki sposób JVM lub jakiś kontener kontroluje wystąpienia naszego Enums, ale wydaje się, że już używa niejawnego Singleton Pattern, różnica polega na tym, że nie wywołujemy a getInstance, po prostu wywołujemy Enum.

Bruno Franco
źródło
3

Jak do pewnego stopnia wspomniano wcześniej, wyliczenie jest klasą Java ze specjalnym warunkiem, że jej definicja musi zaczynać się od co najmniej jednej „stałej wyliczenia”.

Poza tym, a wyliczenia nie mogą być rozszerzane ani używane do rozszerzania innych klas, wyliczenie jest klasą jak każda inna klasa i można jej używać, dodając metody poniżej definicji stałych:

public enum MySingleton {
    INSTANCE;

    public void doSomething() { ... }

    public synchronized String getSomething() { return something; }

    private String something;
}

Dostęp do metod singletona można uzyskać w następujący sposób:

MySingleton.INSTANCE.doSomething();
String something = MySingleton.INSTANCE.getSomething();

Użycie wyliczenia zamiast klasy polega, jak wspomniano w innych odpowiedziach, głównie na bezpieczną wątkowo instancję singletona i gwarancję, że będzie to zawsze tylko jedna kopia.

I, być może, co najważniejsze, to zachowanie jest gwarantowane przez samą maszynę JVM i specyfikację Java.

Oto sekcja ze specyfikacji Java na temat zapobiegania wielu wystąpieniom instancji wyliczeniowej:

Typ wyliczeniowy nie ma innych wystąpień niż te zdefiniowane przez jego stałe wyliczeniowe. Próba jawnego utworzenia wystąpienia typu wyliczeniowego jest błędem czasu kompilacji. Ostateczna metoda klonowania w Enum zapewnia, że ​​stałe wyliczenia nie mogą być klonowane, a specjalne traktowanie przez mechanizm serializacji zapewnia, że ​​zduplikowane wystąpienia nigdy nie są tworzone w wyniku deserializacji. Odblaskowe tworzenie instancji typów wyliczeniowych jest zabronione. Razem te cztery rzeczy zapewniają, że żadne wystąpienia typu wyliczenia nie istnieją poza tymi zdefiniowanymi przez stałe wyliczenia.

Warto zauważyć, że po utworzeniu instancji wszelkie problemy związane z bezpieczeństwem wątków muszą być obsługiwane jak w każdej innej klasie za pomocą słowa kluczowego synchronized itp.

Erk
źródło