Ein Singleton ist eine Klasse, von der es in einer Anwendung nur ein Exemplar gibt. Nützlich ist das für Dinge, die es nur genau einmal in einer Applikation geben soll, und davon gib es einige Beispiele:
· Eine grafische Anwendung hat nur ein Fenster.
· Eine Konsolenanwendung hat nur je einen Eingabe-/Ausgabestrom.
· Alle Druckaufträge wandern in eine Drucker-Warteschlage.
Unbestreitbar ist, dass es einmalige Objekte gibt, variantenreich ist jedoch der Weg dahin. Im Prinzip lässt sich unterscheiden zwischen einem Ansatz, bei dem
a) ein Framework sich um den einmaligen Aufbau des Objekts kümmert und dann auf Anfrage das Objekt liefert oder
b) wir selbst in Java-Code ein Singleton realisieren.
Die bessere Lösung ist ein Framework zu nutzen, namentlich CDI, Guice, Spring, Java EE, doch Java SE enthält keines davon, weswegen wir zur Demonstration den expliziten Weg gehen.
Die technischen Realisierungen sind vielseitig; in Java bieten sich zur Realisierung von Singletons Aufzählungen (enum) und normale Klassen an. In Folgendem wollen wir ein Szenario annehmen, bei dem eine Anwendung zentral auf Konfigurationsdaten zurückgreifen möchte.
Singletons über Aufzählungen
Eine guter Weg für Singletons bieten Aufzählungen – auf den ersten Blick scheint ein enum nicht dafür gemacht, denn eine Aufzählung impliziert ja irgendwie mehr als ein Element – doch die Eigenschaften vom enum sind perfekt für ein Singleton. Die Idee dabei ist, genau ein Element anzubieten, gerne INSTANCE genannt, was letztendlich ein Exemplar der Aufzählungskasse wird, und die normalen Methoden:
public enum Configuration {
INSTANCE;
private Properties props = new Properties( System.getProperties() );
public String getVersion() {
return "1.2";
}
public String getUserDir() {
return props.getProperty( "user.dir" );
}
}
Der Typ Configuration deklariert neben der später öffentlichen statischen Variable INSTANCE auch noch eine interne Variable props, die von der Aufzählung genutzt werden kann, um dort Zustände abzulegen oder zu erfragen. Wir machen das im Beispiel nur lesend über getUserDir().
Ein Nutzer greift wie üblich auf die enum-Eigenschaften zu:
System.out.println( Configuration.INSTANCE.getVersion() ); // 1.2
System.out.println( Configuration.INSTANCE.getUserDir() ); // C:\Users\…
Singletons über Klassen
Ein alternativer Weg – und der übliche vor Java 5 – arbeit mit einer Klasse und privatem Konstruktor, zusammen mit einer statischen Anfrage-Methode, die das Objekt liefert:
public class Configuration2 {
private static final Configuration2 INSTANCE = new Configuration2();
public final static Configuration2 getInstance() {
return INSTANCE;
}
private Configuration2() {
}
private Properties props = new Properties( System.getProperties() );
public String getVersion() {
return "1.2";
}
public String getUserDir() {
return props.getProperty( "user.dir" );
}
}
Interessant sind einmal der private Konstruktor und zum anderen die statische Anfrage-Methode getInstance(). Wenn ein Konstruktor privat ist, bedeutet das noch lange nicht, dass keine Exemplare mehr erzeugt werden können. Ein privater Konstruktor besagt nur, dass er von außen nicht sichtbar ist – aber die Klasse selbst kann ihn ebenso wie private Methoden »sehen« und zur Objekterzeugung nutzen. Objektmethoden kommen dafür nicht in Frage, da ähnlich wie beim Henne-Ei-Problem ja vorher ein Objekt nötig wäre. Es bleiben somit die statischen Methoden als Erzeuger. Und das ist das, was wir wollen: Keine Exemplare von außen, nur von innen. Und da die statische Variable INSTANCE ja genau ein Objekt referenziert, kann die statische Methode diese Referenz nach außen geben.
Die Nutzung der zweiten Variante ist nicht sonderlich unterschiedlich, hat aber wohl eine andere Syntax, sodass ein Refactoring von einer Lösung in die andere Codeänderungen nach sich zieht:
System.out.println( Configuration2.getInstance().getVersion() ); // 1.2
System.out.println( Configuration2.getInstance().getUserDir() ); // C:\Users\…
Oftmals findet sich in Implementierungen eines Singletons noch eine Optimierung, in dem erst in getInstance() das Exemplar aufgebaut wird. Dazu muss aber noch die Methode mit synchronized ausgezeichnet werden, was vor nebenläufigen Zugriffen schützt, sodass nur ein Thread die Methode betreten kann und ein potenziell anderer Thread so lange warten muss, bis der erste Thread die Methode wieder verlassen hat.