Javaでイミュータブルクラスを作成する方法

はじめに

この記事では、Javaプログラミングにおいて不変クラスを作成する方法について概説しています。

オブジェクトが初期化された後にその状態が変化しない場合、そのオブジェクトは不変(immutable)です。例えば、Stringは不変クラスであり、一度インスタンスが生成されると、Stringオブジェクトの値は変わりません。JavaにおけるStringクラスがなぜ不変なのか、詳しく学んでください。

不変なオブジェクトは更新できないため、プログラムは状態の変更ごとに新しいオブジェクトを作成する必要があります。ただし、不変なオブジェクトには以下の利点もあります。

  • An immutable class is good for caching purposes because you don’t have to worry about the value changes.
  • An immutable class is inherently thread-safe, so you don’t have to worry about thread safety in multi-threaded environments.

Javaのマルチスレッディングについてもっと学び、Javaマルチスレッディングのインタビューに関する質問を閲覧してください。

Javaにおいてイミュータブルクラスを作成する

Javaでイミュータブルなクラスを作成するには、以下の一般的な原則に従う必要があります。

    1. クラスをファイナルとして宣言して、拡張できなくする。

 

    1. すべてのフィールドをプライベートにし、直接アクセスを許さないようにする。

 

    1. 変数に対するセッターメソッドを提供しない。

 

    1. 変更可能なフィールドをすべてファイナルとして、値を一度だけ割り当てられるようにする。

 

    1. フィールドの初期化は、ディープコピーを実行するコンストラクタメソッドを使用して行う。

 

    ゲッターメソッドではオブジェクトのクローンを作成し、実際のオブジェクト参照を返さないようにする。

以下のクラスは、イミュータビリティの基礎を示す例です。FinalClassExampleクラスはフィールドを定義し、オブジェクトを初期化するためにディープコピーを使用するコンストラクタメソッドを提供します。FinalClassExample.javaファイルのmainメソッドのコードは、オブジェクトのイミュータビリティをテストします。

「FinalClassExample.java」という新しいファイルを作成し、以下のコードをコピーしてください。

FinalClassExample.javaの最終クラスの例
import java.util.HashMap;
import java.util.Iterator;

public final class FinalClassExample {

	// fields of the FinalClassExample class
	private final int id;
	
	private final String name;
	
	private final HashMap<String,String> testMap;

	
	public int getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	// Getter function for mutable objects

	public HashMap<String, String> getTestMap() {
		return (HashMap<String, String>) testMap.clone();
	}

	// Constructor method performing deep copy
	
	public FinalClassExample(int i, String n, HashMap<String,String> hm){
		System.out.println("Performing Deep Copy for Object initialization");

		// "this" keyword refers to the current object
		this.id=i;
		this.name=n;

		HashMap<String,String> tempMap=new HashMap<String,String>();
		String key;
		Iterator<String> it = hm.keySet().iterator();
		while(it.hasNext()){
			key=it.next();
			tempMap.put(key, hm.get(key));
		}
		this.testMap=tempMap;
	}

	// Test the immutable class

	public static void main(String[] args) {
		HashMap<String, String> h1 = new HashMap<String,String>();
		h1.put("1", "first");
		h1.put("2", "second");
		
		String s = "original";
		
		int i=10;
		
		FinalClassExample ce = new FinalClassExample(i,s,h1);
		
		// print the ce values
		System.out.println("ce id: "+ce.getId());
		System.out.println("ce name: "+ce.getName());
		System.out.println("ce testMap: "+ce.getTestMap());
		// change the local variable values
		i=20;
		s="modified";
		h1.put("3", "third");
		// print the values again
		System.out.println("ce id after local variable change: "+ce.getId());
		System.out.println("ce name after local variable change: "+ce.getName());
		System.out.println("ce testMap after local variable change: "+ce.getTestMap());
		
		HashMap<String, String> hmTest = ce.getTestMap();
		hmTest.put("4", "new");
		
		System.out.println("ce testMap after changing variable from getter methods: "+ce.getTestMap());

	}

}

プログラムをコンパイルして実行してください。

  1. javac FinalClassExample.java
  2. java FinalClassExample

 

Note

注意:ファイルのコンパイル時に次のメッセージが表示される場合があります。注意:getterメソッドがHashMap<String, String>からObjectへのunchecked castを使用しているため、FinalClassExample.javaはuncheckedまたはunsafeな操作を使用しています。この例の目的では、コンパイラの警告を無視できます。

以下の出力が得られます。

Output

Performing Deep Copy for Object initialization ce id: 10 ce name: original ce testMap: {1=first, 2=second} ce id after local variable change: 10 ce name after local variable change: original ce testMap after local variable change: {1=first, 2=second} ce testMap after changing variable from getter methods: {1=first, 2=second}

出力を示すと、HashMapの値は変わっていません。なぜなら、コンストラクタがディープコピーを使用しており、ゲッター関数は元のオブジェクトのクローンを返すからです。

ディープコピーとクローニングを使わない場合、何が起こるのですか?

FinalClassExample.javaファイルに変更を加えて、ディープコピーの代わりにシャローコピーを使用し、オブジェクトではなくコピーを返すとどうなるかを示すことができます。オブジェクトはもはや不変ではなく、変更することができます。例ファイルに以下の変更を加えてください(またはコード例からコピー&ペーストしてください)。

  • Delete the constructor method providing deep copy and add the constructor method providing shallow copy that is highlighted in the following example.
  • In the getter function, delete return (HashMap<String, String>) testMap.clone(); and add return testMap;.

例のファイルは今、このようになっています。

ファイナルクラスの例、FinalClassExample.java
import java.util.HashMap;
import java.util.Iterator;

public final class FinalClassExample {

	// fields of the FinalClassExample class
	private final int id;
	
	private final String name;
	
	private final HashMap<String,String> testMap;

	
	public int getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	// Getter function for mutable objects

	public HashMap<String, String> getTestMap() {
		return testMap;
	}

	//Constructor method performing shallow copy

	public FinalClassExample(int i, String n, HashMap<String,String> hm){
		System.out.println("Performing Shallow Copy for Object initialization");
		this.id=i;
		this.name=n;
		this.testMap=hm;
	}

	// Test the immutable class

	public static void main(String[] args) {
		HashMap<String, String> h1 = new HashMap<String,String>();
		h1.put("1", "first");
		h1.put("2", "second");
		
		String s = "original";
		
		int i=10;
		
		FinalClassExample ce = new FinalClassExample(i,s,h1);
		
		// print the ce values
		System.out.println("ce id: "+ce.getId());
		System.out.println("ce name: "+ce.getName());
		System.out.println("ce testMap: "+ce.getTestMap());
		// change the local variable values
		i=20;
		s="modified";
		h1.put("3", "third");
		// print the values again
		System.out.println("ce id after local variable change: "+ce.getId());
		System.out.println("ce name after local variable change: "+ce.getName());
		System.out.println("ce testMap after local variable change: "+ce.getTestMap());
		
		HashMap<String, String> hmTest = ce.getTestMap();
		hmTest.put("4", "new");
		
		System.out.println("ce testMap after changing variable from getter methods: "+ce.getTestMap());

	}

}

プログラムをコンパイルして実行してください。

  1. javac FinalClassExample.java
  2. java FinalClassExample

 

あなたは次の出力を受け取ります。

Output

Performing Shallow Copy for Object initialization ce id: 10 ce name: original ce testMap: {1=first, 2=second} ce id after local variable change: 10 ce name after local variable change: original ce testMap after local variable change: {1=first, 2=second, 3=third} ce testMap after changing variable from getter methods: {1=first, 2=second, 3=third, 4=new}

出力結果は、コンストラクタメソッドが浅いコピーを使用しているため、HashMapの値が変更されたことを示しています。また、ゲッター関数には元のオブジェクトへの直接参照があります。

結論

Javaで不変クラスを作成する際には、ディープコピーの重要性を含む一般的な原則のいくつかを学びました。さらにJavaのチュートリアルで学びを深めましょう。

コメントを残す 0

Your email address will not be published. Required fields are marked *