Du kanske har hört att Singleton single malt skotsk whisky är bra? Tja, alkohol är dåligt för din hälsa, så idag kommer vi att berätta om singeldesignmönstret i Java istället.

Vi har tidigare granskat skapandet av objekt, så vi vet att för att skapa ett objekt i Java måste du skriva något i stil med:


Robot robot = new Robot(); 
    

Men vad händer om vi vill säkerställa att endast en instans av klassen skapas?

Den nya Robot() -satsen kan skapa många objekt, och ingenting hindrar oss från att göra det. Det är här singelmönstret kommer till undsättning.

Anta att du behöver skriva ett program som ansluter till en skrivare - bara EN skrivare - och be den att skriva ut:


public class Printer { 
 
	public Printer() { 
	} 
     
	public void print() { 
    	… 
	} 
}
    

Det här ser ut som en vanlig klass... MEN! Det finns ett "men": jag kan skapa flera instanser av mitt skrivarobjekt och anropa metoder på dem på olika ställen. Detta kan skada eller till och med gå sönder min skrivare. Så vi måste se till att det bara finns en instans av vår skrivare, och det är vad en singleton kommer att göra för oss!

Sätt att skapa en singleton

Det finns två sätt att skapa en singleton:

  • använd en privat konstruktör;
  • exportera en offentlig statisk metod för att ge tillgång till en enda instans.

Låt oss först överväga att använda en privat konstruktör. För att göra detta måste vi förklara ett fält som final i vår klass och initialisera det. Eftersom vi markerade det som slutgiltigt vet vi att det kommer att vara oföränderligt , dvs vi kan inte längre ändra det.

Du måste också deklarera konstruktorn som privat för att förhindra att objekt skapas utanför klassen . Detta garanterar för oss att det inte kommer att finnas några andra instanser av vår skrivare i programmet. Konstruktören kommer bara att anropas en gång under initialiseringen och kommer att skapa vår skrivare :


public class Printer { 
     
	public static final Printer PRINTER = new Printer(); 
     
	private Printer() { 
	} 
 
	public void print() { 
        // Printing... 
 
	} 
}
    

Vi använde en privat konstruktör för att skapa en PRINTER-singel - det kommer bara att finnas en instans. DeSKRIVAREvariabeln har den statiska modifieraren , eftersom den inte tillhör något objekt, utan till själva klassen Printer .

Låt oss nu överväga att skapa en singleton med en statisk metod för att ge tillgång till en enda instans av vår klass (och notera att fältet nu är privat ):


public class Printer { 
 
	private static final Printer PRINTER = new Printer(); 
 
	private Printer() { 
	} 
 
	public static Printer getInstance() { 
    	return PRINTER; 
	} 
     
	public void print() { 
        // Printing... 
	} 
} 
    

Oavsett hur många gånger vi anropar getInstance()- metoden här, kommer vi alltid att få sammaSKRIVAREobjekt.

Att skapa en singel med en privat konstruktör är enklare och mer kortfattat. Dessutom är API:et uppenbart, eftersom det offentliga fältet deklareras som slutgiltigt , vilket garanterar att det alltid kommer att innehålla en referens till samma objekt.

Alternativet statisk metod ger oss flexibiliteten att ändra singeltonen till en icke-singletonklass utan att ändra dess API. Metoden getInstance () ger oss en enda instans av vårt objekt, men vi kan ändra den så att den returnerar en separat instans för varje användare som anropar det.

Det statiska alternativet låter oss också skriva en generisk singelfabrik.

Den sista fördelen med det statiska alternativet är att du kan använda det med en metodreferens.

Om du inte behöver någon av ovanstående fördelar rekommenderar vi att du använder alternativet som involverar ett offentligt fält.

Om vi ​​behöver serialisering kommer det inte att räcka med att bara implementera det serialiserade gränssnittet. Vi måste också lägga till metoden readResolve , annars får vi en ny singleton-instans under deserialiseringen.

Serialisering behövs för att spara ett objekts tillstånd som en sekvens av byte, och deserialisering behövs för att återställa objektet från dessa byte. Du kan läsa mer om serialisering och deserialisering i den här artikeln .

Låt oss nu skriva om vår singel:


public class Printer implements Serializable { 
 
	private static final Printer PRINTER = new Printer(); 
 
	private Printer() { 
	} 
 
	public static Printer getInstance() { 
    	return PRINTER; 
	} 
} 
    

Nu ska vi serialisera och deserialisera det.

Observera att exemplet nedan är standardmekanismen för serialisering och deserialisering i Java. En fullständig förståelse för vad som händer i koden kommer efter att du studerat "I/O-strömmar" (i Java Syntax-modulen) och "Serialisering" (i Java Core-modulen).

var printer = Printer.getInstance(); 
var fileOutputStream = new FileOutputStream("printer.txt"); 
var objectOutputStream = new ObjectOutputStream(fileOutputStream); 
objectOutputStream.writeObject(printer); 
objectOutputStream.close(); 
 
var fileInputStream = new FileInputStream("printer.txt"); 
var objectInputStream = new ObjectInputStream(fileInputStream); 
var deserializedPrinter =(Printer) objectInputStream.readObject(); 
objectInputStream.close(); 
 
System.out.println("Singleton 1 is: " + printer); 
System.out.println("Singleton 2 is: " + deserializedPrinter);
    

Och vi får detta resultat:

Singleton 1 är: Printer@6be46e8f
Singleton 2 är: Printer@3c756e4d

Här ser vi att deserialisering gav oss en annan instans av vår singel. För att fixa detta, låt oss lägga till readResolve- metoden till vår klass:


public class Printer implements Serializable { 
 
	private static final Printer PRINTER = new Printer(); 
 
	private Printer() { 
	} 
 
	public static Printer getInstance() { 
    	return PRINTER; 
	} 
 
	public Object readResolve() { 
    	return PRINTER; 
	} 
}
    

Nu ska vi serialisera och deserialisera vår singleton igen:


var printer = Printer.getInstance(); 
var fileOutputStream = new FileOutputStream("printer.txt"); 
var objectOutputStream = new ObjectOutputStream(fileOutputStream); 
objectOutputStream.writeObject(printer); 
objectOutputStream.close(); 
 
var fileInputStream = new FileInputStream("printer.txt"); 
var objectInputStream = new ObjectInputStream(fileInputStream); 
var deserializedPrinter=(Printer) objectInputStream.readObject(); 
objectInputStream.close(); 
 
System.out.println("Singleton 1 is: " + printer); 
System.out.println("Singleton 2 is: " + deserializedPrinter); 
    

Och vi får:

Singleton 1 är: com.company.Printer@6be46e8f
Singleton 2 är: com.company.Printer@6be46e8f

Metoden readResolve() låter oss få samma objekt som vi deserialiserade, och förhindrar därigenom skapandet av oseriösa singletons.

Sammanfattning

Idag lärde vi oss om singlar: hur man skapar dem och när man använder dem, vad de är till för och vilka alternativ Java erbjuder för att skapa dem. De specifika egenskaperna för båda alternativen ges nedan:

Privat konstruktör Statisk metod
  • Enklare och mer kortfattad
  • Uppenbart API eftersom singletonfältet är offentligt final
  • Kan användas med en metodreferens
  • Kan användas för att skriva en generisk singelfabrik
  • Kan användas för att returnera en separat instans för varje användare