イントロダクション
この記事では、Javaプログラミングでイミュータブルなクラスを作成する方法について概説します。
オブジェクトが初期化された後に状態が変化しない場合、そのオブジェクトはイミュータブルです。例えば、String
はイミュータブルなクラスであり、インスタンス化されるとString
オブジェクトの値は変わりません。JavaにおけるString
クラスがなぜイミュータブルなのかについて詳しく学びましょう。
イミュータブルなオブジェクトは更新できないため、状態の変更ごとに新しいオブジェクトを作成する必要があります。しかし、イミュータブルなオブジェクトには以下の利点もあります:
- 値の変更について心配する必要がないため、キャッシュ目的に適しています。
- イミュータブルなクラスは元々スレッドセーフなので、マルチスレッド環境でのスレッドセーフ性について心配する必要がありません。
Javaのマルチスレッドについて詳しく学び、Javaマルチスレッドのインタビュー質問を参照してください。
Java でイミュータブルクラスを作成する
Java でイミュータブルクラスを作成するには、次の一般的な原則に従う必要があります:
- クラスを
final
として宣言して、拡張できないようにします。 - すべてのフィールドを
private
にすることで、直接アクセスが許可されないようにします。 - 変数のためのセッターメソッドを提供しないでください。
- すべての可変フィールドを
final
にすることで、フィールドの値が一度だけ代入されるようにします。 - すべてのフィールドをディープコピーを行うコンストラクタメソッドを使用して初期化します。
- ゲッターメソッドでオブジェクトのクローンを作成し、実際のオブジェクト参照ではなくコピーを返します。
次のクラスは、イミュータビリティの基本を示す例です。 FinalClassExample
クラスは、フィールドを定義し、オブジェクトを初期化するためにディープコピーを使用するコンストラクタメソッドを提供します。 FinalClassExample.java
ファイルの main
メソッドのコードは、オブジェクトのイミュータビリティをテストします。
FinalClassExample.java
という新しいファイルを作成し、次のコードをコピーしてください:
import java.util.HashMap;
import java.util.Iterator;
public final class FinalClassExample {
// FinalClassExample クラスのフィールド
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 関数
public HashMap<String, String> getTestMap() {
return (HashMap<String, String>) testMap.clone();
}
// ディープコピーを実行するコンストラクタメソッド
public FinalClassExample(int i, String n, HashMap<String,String> hm){
System.out.println("Performing Deep Copy for Object initialization");
// "this" キーワードは現在のオブジェクトを指します
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;
}
// Immutable クラスをテストする
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);
// ce の値を表示
System.out.println("ce id: "+ce.getId());
System.out.println("ce name: "+ce.getName());
System.out.println("ce testMap: "+ce.getTestMap());
// ローカル変数の値を変更
i=20;
s="modified";
h1.put("3", "third");
// 再び値を表示
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());
}
}
プログラムをコンパイルして実行:
- javac FinalClassExample.java
- java FinalClassExample
注意:ファイルをコンパイルすると、次のメッセージが表示される可能性があります:Note: FinalClassExample.java uses unchecked or unsafe operations
、なぜなら Getter メソッドが HashMap<String,String>
から Object
への未検査キャストを使用しているためです。この例の目的では、コンパイラの警告を無視できます。
次の出力が得られます:
OutputPerforming 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}
出力は、コンストラクタがディープコピーを使用し、Getter 関数が元のオブジェクトのクローンを返すため、HashMap の値が変更されていないことを示しています。
ディープコピーとクローンを使用しない場合の動作
import java.util.HashMap;
import java.util.Iterator;
public final class FinalClassExample {
// FinalClassExample クラスのフィールド
private final int id;
private final String name;
private final HashMap<String,String> testMap;
public int getId() {
return id;
}
public String getName() {
return name;
}
// mutable オブジェクトのためのゲッター関数
public HashMap<String, String> getTestMap() {
return testMap;
}
// シャローコピーを実行するコンストラクタメソッド
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;
}
// 不変のクラスをテスト
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);
// ce の値を出力
System.out.println("ce id: "+ce.getId());
System.out.println("ce name: "+ce.getName());
System.out.println("ce testMap: "+ce.getTestMap());
// ローカル変数の値を変更
i=20;
s="modified";
h1.put("3", "third");
// 再び値を出力
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());
}
}
プログラムをコンパイルして実行してください:
- javac FinalClassExample.java
- java FinalClassExample
OutputPerforming 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の値が変更されました。getter関数には元のオブジェクトへの直接参照があります。
結論
Javaで不変のクラスを作成する際に従う一般的な原則のいくつかを学びました。これには、ディープコピーの重要性も含まれます。さらに詳しくは、Javaチュートリアルで学習を続けてください。
Source:
https://www.digitalocean.com/community/tutorials/how-to-create-immutable-class-in-java