シングルトンのシングルモルトスコッチウイスキーが美味しいという話を聞いたことがあるでしょうか?さて、アルコールは健康に悪いので、今日は代わりに Java のシングルトン設計パターンについて説明します。

以前にオブジェクトの作成について検討したので、Java でオブジェクトを作成するには、次のようなものを記述する必要があることがわかりました。

Robot robot = new Robot();

しかし、クラスのインスタンスが 1 つだけ作成されるようにしたい場合はどうすればよいでしょうか?

新しいRobot()ステートメントは多くのオブジェクトを作成でき、それを妨げるものは何もありません。ここでシングルトン パターンが役に立ちます。

プリンター (1 台だけ) に接続し、印刷するように指示するアプリケーションを作成する必要があるとします。

public class Printer {

	public Printer() {
	}

	public void print() {}
}

一見普通の授業のようですが…しかし!「ただし」が 1 つあります。プリンター オブジェクトの複数のインスタンスを作成し、それらのインスタンスのメソッドをさまざまな場所で呼び出すことができます。これにより、プリンターが損傷したり壊れたりする可能性があります。したがって、プリンターのインスタンスが 1 つだけであることを確認する必要があります。これがシングルトンによって行われます。

シングルトンを作成する方法

シングルトンを作成するには 2 つの方法があります。

  • プライベートコンストラクターを使用します。
  • パブリック静的メソッドをエクスポートして、単一のインスタンスへのアクセスを提供します。

まず、プライベート コンストラクターの使用を検討してみましょう。これを行うには、クラス内でフィールドをFinalとして宣言し、初期化する必要があります。これをFinalとしてマークしたので、それがimmutableになることがわかります。つまり、変更できなくなります。

また、クラス外にオブジェクトが作成されないように、コンストラクターをプライベートとして宣言する必要があります。これにより、プログラム内にプリンターの他のインスタンスが存在しないことが保証されます。コンストラクターは初期化中に 1 回だけ呼び出され、Printer を作成します。

public class Printer {

	public static final Printer PRINTER = new Printer();

	private Printer() {
	}

	public void print() {
        // Printing...

	}
}

プライベート コンストラクターを使用して PRINTER シングルトンを作成しました。インスタンスは 1 つだけです。のプリンター変数はどのオブジェクトにも属さず、Printerクラス自体に属しているため、 static 修飾子を持ちます。

次に、静的メソッドを使用してシングルトンを作成し、クラスの単一インスタンスへのアクセスを提供することを考えてみましょう (フィールドがprivateになっていることに注意してください)。

public class Printer {

	private static final Printer PRINTER = new Printer();

	private Printer() {
	}

	public static Printer getInstance() {
    	return PRINTER;
	}

	public void print() {
        // Printing...
	}
}

ここでgetInstance()メソッドを何度呼び出しても、常に同じ結果が得られます。プリンター物体。

プライベートコンストラクターを使用してシングルトンを作成する方が簡単かつ簡潔です。さらに、publicフィールドがFinalとして宣言されているため、API は明らかであり、常に同じオブジェクトへの参照が含まれることが保証されます。

静的メソッド オプションを使用すると、API を変更せずにシングルトンを非シングルトン クラスに変更できる柔軟性が得られます。getInstance ()メソッドはオブジェクトの 1 つのインスタンスを提供しますが、呼び出したユーザーごとに個別のインスタンスを返すように変更することもできます。

静的オプションを使用すると、汎用のシングルトン ファクトリを作成することもできます。

静的オプションの最後の利点は、メソッド参照とともに使用できることです。

上記の利点が必要ない場合は、パブリックフィールドを含むオプションを使用することをお勧めします。

シリアル化が必要な場合は、 Serializableインターフェイスを実装するだけでは十分ではありません。readResolveメソッドも追加する必要があります。追加しないと、逆シリアル化中に新しいシングルトン インスタンスが取得されてしまいます。

シリアル化はオブジェクトの状態をバイトのシーケンスとして保存するために必要であり、逆シリアル化はそれらのバイトからオブジェクトを復元するために必要です。シリアル化と逆シリアル化の詳細については、この記事を参照してください。

次に、シングルトンを書き直してみましょう。

public class Printer implements Serializable {

	private static final Printer PRINTER = new Printer();

	private Printer() {
	}

	public static Printer getInstance() {
    	return PRINTER;
	}
}

次に、シリアル化と逆シリアル化を行います。

以下の例は、Java のシリアル化および逆シリアル化の標準メカニズムであることに注意してください。「I/O ストリーム」(Java 構文モジュール内) と「シリアル化」(Java コア モジュール内) を学習すると、コード内で何が起こっているかを完全に理解できるようになります。
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);

そして、次の結果が得られます。

シングルトン 1 は: Printer@6be46e8f
シングルトン 2 は: Printer@3c756e4d

ここでは、逆シリアル化によってシングルトンの別のインスタンスが得られたことがわかります。これを修正するには、クラスにreadResolveメソッドを追加しましょう。

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;
	}
}

次に、シングルトンを再度シリアル化および逆シリアル化します。

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);

そして、次の結果が得られます。

シングルトン 1 は: com.company.Printer@6be46e8f
シングルトン 2 は: com.company.Printer@6be46e8f

readResolve ()メソッドを使用すると、逆シリアル化したのと同じオブジェクトを取得できるため、不正シングルトンの作成を防ぐことができます。

まとめ

今日はシングルトンについて学びました。シングルトンの作成方法と使用時期、その目的、およびシングルトンを作成するために Java が提供するオプションです。両方のオプションの具体的な機能を以下に示します。

プライベートコンストラクター 静的メソッド
  • より簡単かつ簡潔に
  • シングルトンフィールドはpublic Finalであるため、明らかな API
  • メソッド参照と一緒に使用できます
  • 汎用シングルトン ファクトリの作成に使用できます
  • ユーザーごとに個別のインスタンスを返すために使用できます