Implementieren von Singleton mit einer Aufzählung (in Java)

177

Ich habe gelesen, dass es möglich ist, Singletonin Java Folgendes zu implementieren Enum:

public enum MySingleton {
     INSTANCE;   
}

Aber wie funktioniert das oben genannte? Insbesondere muss ein Objectinstanziiert werden. Wie wird hier MySingletoninstanziiert? Wer macht new MySingleton()?

Anand
quelle
24
Wer macht neue MySingleton () Die JVM ist.
Sotirios Delimanolis
37
INSTANCEist das gleiche wie public static final MySingleton INSTANCE = new MySingleton();.
Pshemo
6
ENUM - Ein garantierter Singleton.
Kein Fehler
Lesen Sie dzone.com/articles/java-singletons-using-enum , warum Sie enum verwenden und nicht die anderen Methoden. Kurz: Probleme treten bei der Verwendung von Serialisierung und Reflexion auf.
Miyagi

Antworten:

202

Dies,

public enum MySingleton {
  INSTANCE;   
}

hat einen impliziten leeren Konstruktor. Machen Sie es stattdessen explizit,

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

Wenn Sie dann eine weitere Klasse mit einer main()Methode wie hinzugefügt haben

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

Du würdest sehen

Here
INSTANCE

enumFelder sind Kompilierungszeitkonstanten, aber sie sind Instanzen ihres enumTyps. Und sie werden erstellt, wenn der Aufzählungstyp zum ersten Mal referenziert wird .

Elliott Frisch
quelle
13
Sie sollten hinzufügen, dass Aufzählungen standardmäßig einen impliziten privaten Konstruktor haben und dass das explizite Hinzufügen eines privaten Konstruktors nicht erforderlich ist, es sei denn, Sie haben tatsächlich Code, den Sie in diesem Konstruktor ausführen müssen
Nimrod Dayan
Jedes Enum-Feld erstellt eine Instanz nur einmal, sodass kein privater Konstruktor erstellt werden muss.
Pravat Panda
2
öffentliche Aufzählung MySingleton {INSTANCE, INSTANCE1; } und dann System.out.println (MySingleton.INSTANCE.hashCode ()); System.out.println (MySingleton.INSTANCE1.hashCode ()); Es werden verschiedene Hashcodes gedruckt. Bedeutet dies, dass zwei Objekte von MySingleton erstellt werden?
Scott Miles
@scottmiles Ja. Weil Sie zwei Instanzen haben. Ein Singleton hat per Definition einen.
Elliott Frisch
Der privateModifikator hat für einen enumKonstruktor keine Bedeutung und ist vollständig redundant.
Dmitri Nesteruk
75

Ein enumTyp ist ein spezieller Typ von class.

Ihr enumWille wird tatsächlich zu so etwas kompiliert

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

Wenn Ihr Code zum ersten Mal aufgerufen wird INSTANCE, wird die Klasse MySingletonvon der JVM geladen und initialisiert. Dieser Vorgang initialisiert das staticobige Feld einmal (träge).

Sotirios Delimanolis
quelle
Soll diese Klasse auch den privaten Konstruktor () enthalten? Ich denke, es wäre
Yasir Shabbir Choudhary
public enum MySingleton { INSTANCE,INSTANCE1; }und dann werden System.out.println(MySingleton.INSTANCE.hashCode()); System.out.println(MySingleton.INSTANCE1.hashCode());verschiedene Hashcodes gedruckt. Bedeutet dies, dass zwei Objekte von MySingleton erstellt werden?
Scott Miles
2
@scottmiles Ja, das sind nur zwei Differenzkonstanten enum.
Sotirios Delimanolis
2
@caf, sicher. Siehe hier: docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.9.3
Sotirios Delimanolis
@SotiriosDelimanolis, ist dieser Ansatz mit Enum-Thread sicher?
Abg
63

In diesem Java Best Practices-Buch von Joshua Bloch finden Sie Erklärungen, warum Sie die Singleton-Eigenschaft mit einem privaten Konstruktor oder einem Enum-Typ erzwingen sollten. Das Kapitel ist ziemlich lang, daher fasst es zusammen:

Wenn Sie eine Klasse zu einem Singleton machen, kann es schwierig sein, ihre Clients zu testen, da es unmöglich ist, einen Singleton durch eine Scheinimplementierung zu ersetzen, wenn nicht eine Schnittstelle implementiert wird, die als Typ dient. Der empfohlene Ansatz besteht darin, Singletons zu implementieren, indem einfach ein Aufzählungstyp mit einem Element erstellt wird:

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

Dieser Ansatz ist funktional äquivalent zum Public-Field-Ansatz, mit der Ausnahme, dass er präziser ist, die Serialisierungsmaschinerie kostenlos zur Verfügung stellt und eine ironclad-Garantie gegen Mehrfachinstanziierung bietet, selbst angesichts ausgefeilter Serialisierungs- oder Reflexionsangriffe.

Während dieser Ansatz noch nicht weit verbreitet ist, ist ein Aufzählungstyp mit einem Element der beste Weg, um einen Singleton zu implementieren.

ekostadinov
quelle
Ich denke, um einen Singleton testbar zu machen, könnten Sie ein Strategiemuster verwenden und alle Aktionen des Singletons durch die Strategie laufen lassen. Sie können also eine "Produktions" -Strategie und eine "Test" -Strategie haben ...
Erk
9

Wie alle Enum-Instanzen instanziiert Java jedes Objekt, wenn die Klasse geladen wird, mit der Garantie, dass es genau einmal pro JVM instanziiert wird . Stellen Sie sich die INSTANCEDeklaration als ein öffentliches statisches Endfeld vor: Java instanziiert das Objekt, wenn die Klasse zum ersten Mal referenziert wird.

Die Instanzen werden während der statischen Initialisierung erstellt, die in der Java-Sprachspezifikation, Abschnitt 12.4 definiert ist .

Für das, was es wert ist, beschreibt Joshua Bloch dieses Muster ausführlich als Punkt 3 der Effective Java Second Edition .

Jeff Bowman
quelle
6

Da es bei Singleton Pattern darum geht, einen privaten Konstruktor zu haben und eine Methode zur Steuerung der Instanziierungen getInstanceaufzurufen (wie einige ), haben wir in Enums bereits einen impliziten privaten Konstruktor.

Ich weiß nicht genau, wie die JVM oder ein Container die Instanzen von uns steuert Enums, aber es scheint, dass sie bereits ein implizites verwenden Singleton Pattern. Der Unterschied ist, dass wir nicht a nennen , sondern getInstancenur Enum.

Bruno Franco
quelle
3

Wie bereits erwähnt, ist eine Aufzählung eine Java-Klasse mit der besonderen Bedingung, dass ihre Definition mit mindestens einer "Aufzählungskonstante" beginnen muss.

Abgesehen davon, dass Aufzählungen nicht erweitert oder zum Erweitern anderer Klassen verwendet werden können, ist eine Aufzählung eine Klasse wie jede andere Klasse, und Sie verwenden sie, indem Sie Methoden unterhalb der Konstantendefinitionen hinzufügen:

public enum MySingleton {
    INSTANCE;

    public void doSomething() { ... }

    public synchronized String getSomething() { return something; }

    private String something;
}

Sie greifen auf die folgenden Methoden des Singletons zu:

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

Bei der Verwendung einer Aufzählung anstelle einer Klasse geht es, wie in anderen Antworten erwähnt, hauptsächlich um eine thread-sichere Instanziierung des Singletons und die Garantie, dass es sich immer nur um eine Kopie handelt.

Und vielleicht am wichtigsten ist, dass dieses Verhalten durch die JVM selbst und die Java-Spezifikation garantiert wird.

Hier ist ein Abschnitt aus der Java-Spezifikation, wie mehrere Instanzen einer Enum-Instanz verhindert werden:

Ein Aufzählungstyp hat keine anderen Instanzen als die durch seine Aufzählungskonstanten definierten. Es ist ein Fehler beim Kompilieren, wenn versucht wird, einen Aufzählungstyp explizit zu instanziieren. Die endgültige Klonmethode in Enum stellt sicher, dass Enum-Konstanten niemals geklont werden können, und die spezielle Behandlung durch den Serialisierungsmechanismus stellt sicher, dass durch Deserialisierung niemals doppelte Instanzen erstellt werden. Die reflektierende Instanziierung von Aufzählungstypen ist verboten. Zusammen stellen diese vier Dinge sicher, dass keine Instanzen eines Aufzählungstyps existieren, die über die durch die Aufzählungskonstanten definierten hinausgehen.

Bemerkenswert ist, dass nach der Instanziierung alle Thread-Sicherheitsbedenken wie in jeder anderen Klasse mit dem synchronisierten Schlüsselwort usw. behandelt werden müssen.

Erk
quelle