Java Language
不変オブジェクト
サーチ…
備考
不変オブジェクトは固定状態(セッターなし)なので、オブジェクト作成時にはすべての状態を知る必要があります。
技術的には必要ではありませんが、すべてのフィールドをfinal
にすることがベストプラクティスです。これにより、不変クラスがスレッドセーフになります(Java Concurrency in Practice、3.4.1を参照)。
この例は、これを達成するのに役立ついくつかのパターンを示しています。
防御的なコピーを使用してタイプの不変バージョンを作成する。
Javaの基本的な型とクラスの中には基本的に変更可能なものがあります。たとえば、すべての配列型は変更可能であり、 java.util.Data
などのクラスも変更可能です。これは、不変型が義務付けられている状況では扱いにくいことがあります。
これに対処する1つの方法は、可変型の不変ラッパーを作成することです。整数の配列の単純なラッパーです
public class ImmutableIntArray {
private final int[] array;
public ImmutableIntArray(int[] array) {
this.array = array.clone();
}
public int[] getValue() {
return this.clone();
}
}
このクラスは防御的なコピーを使用して、変更可能な状態( int[]
)を変更する可能性のあるコードから隔離します。
コンストラクタは
clone()
を使用してパラメータ配列の個別のコピーを作成します。後でコンストラクタの呼び出し元がパラメータ配列を変更した場合、それはImmutableIntArray
状態に影響しません。また、
getValue()
メソッドはclone()
を使用して返される配列を作成します。呼び出し元が結果配列を変更した場合は、ImmutableIntArray
状態に影響しません。
ラップされた配列に対して読み取り専用操作を実行するために、メソッドをImmutableIntArray
に追加することもできます。例えば、長さを取得したり、特定のインデックスで値を取得したりするなどです。
このように実装された不変ラッパー型は、元の型と型互換性がないことに注意してください。後者のために前者を単に置き換えることはできません。
不変クラスのレシピ
不変オブジェクトは、状態を変更できないオブジェクトです。不変クラスは、そのインスタンスが設計および実装によって不変であるクラスです。不変性の例として最も一般的に提示されるJavaクラスはjava.lang.Stringです。
以下は、ステレオタイプの例です。
public final class Person {
private final String name;
private final String ssn; // (SSN == social security number)
public Person(String name, String ssn) {
this.name = name;
this.ssn = ssn;
}
public String getName() {
return name;
}
public String getSSN() {
return ssn;
}
}
これのバリエーションは、コンストラクタをprivate
として宣言し、代わりにpublic static
factoryメソッドを提供することです。
不変クラスの標準レシピは次のとおりです。
- すべてのプロパティは、コンストラクタまたはファクトリメソッドで設定する必要があります。
- セッターはいないはずです。
- インタフェースの互換性のためにsetterを含める必要がある場合は、何もしないか、例外をスローする必要があります。
- すべてのプロパティは
private
でfinal
として宣言する必要があります。 - 変更可能な型への参照であるすべてのプロパティ:
- プロパティは、コンストラクタを介して渡された値のディープコピーで初期化される必要があります。
- プロパティのgetterはプロパティ値の詳細コピーを返す必要があります。
- クラスは、不変クラスの変更可能なサブクラスを作成しないように
final
として宣言する必要があります。
注意すべき他のいくつかの事柄:
- 不変性は、オブジェクトがヌル可能であることを妨げません。例えば
null
をString
変数に割り当てることができます。 - 不変クラスのプロパティが
final
として宣言されている場合、インスタンスは本質的にスレッドセーフです。これにより、不変クラスはマルチスレッドアプリケーションを実装するための優れたビルディングブロックになります。
クラスが不変にならないようにする典型的な設計上の欠陥
いくつかのセッターを使用して、コンストラクターに必要なすべてのプロパティーを設定せずに、
public final class Person { // example of a bad immutability
private final String name;
private final String surname;
public Person(String name) {
this.name = name;
}
public String getName() { return name;}
public String getSurname() { return surname;}
public void setSurname(String surname) { this.surname = surname); }
}
Person
クラスが不変でないことを示すのは簡単です:
Person person = new Person("Joe");
person.setSurname("Average"); // NOT OK, change surname field after creation
これを修正するには、単にsetSurname()
を削除し、次のようにコンストラクタをリファクタリングします。
public Person(String name, String surname) {
this.name = name;
this.surname = surname;
}
インスタンス変数を非公開および最終としてマークしない
次のクラスを見てください:
public final class Person {
public String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
以下のスニペットは、上記のクラスが不変でないことを示しています:
Person person = new Person("Average Joe");
person.name = "Magic Mike"; // not OK, new name for person after creation
これを修正するには、単にnameプロパティをprivate
とfinal
としてマークします。
クラスの可変オブジェクトをゲッターに公開する
次のクラスを見てください:
import java.util.List;
import java.util.ArrayList;
public final class Names {
private final List<String> names;
public Names(List<String> names) {
this.names = new ArrayList<String>(names);
}
public List<String> getNames() {
return names;
}
public int size() {
return names.size();
}
}
Names
クラスは、最初の視点では不変のようですが、次のコードが示すようにではありません。
List<String> namesList = new ArrayList<String>();
namesList.add("Average Joe");
Names names = new Names(namesList);
System.out.println(names.size()); // 1, only containing "Average Joe"
namesList = names.getNames();
namesList.add("Magic Mike");
System.out.println(names.size()); // 2, NOT OK, now names also contains "Magic Mike"
これは、 getNames()
によって返された参照リストを変更すると、実際のNames
リストを変更できるためです。
これを修正するには、防御的なコピーを作成するか 、次のようにクラスの可変オブジェクトを参照する参照を返さないでください。
public List<String> getNames() {
return new ArrayList<String>(this.names); // copies elements
}
または 、次のように他の唯一の不変オブジェクトとプリミティブが 、返される方法でゲッターを設計することによって:
public String getName(int index) {
return names.get(index);
}
public int size() {
return names.size();
}
不変クラス外で変更可能なオブジェクトをコンストラクタに挿入する
これは以前の欠陥のバリエーションです。次のクラスを見てください:
import java.util.List;
public final class NewNames {
private final List<String> names;
public Names(List<String> names) {
this.names = names;
}
public String getName(int index) {
return names.get(index);
}
public int size() {
return names.size();
}
}
前のNames
クラスと同様に、 NewNames
クラスも最初の視点では不変であるようですが、実際には次のスニペットはそれとは逆のことを証明しています:
List<String> namesList = new ArrayList<String>();
namesList.add("Average Joe");
NewNames names = new NewNames(namesList);
System.out.println(names.size()); // 1, only containing "Average Joe"
namesList.add("Magic Mike");
System.out.println(names.size()); // 2, NOT OK, now names also contains "Magic Mike"
これを修正するには、前の脆弱性と同じように、オブジェクトを不変クラスに直接割り当てずに防御コピーを作成するだけです。つまり、コンストラクタは次のように変更できます。
public Names(List<String> names) {
this.names = new ArrayList<String>(names);
}
クラスのメソッドをオーバーライドさせる
次のクラスを見てください:
public class Person {
private final String name;
public Person(String name) {
this.name = name;
}
public String getName() { return name;}
}
Person
クラスは、最初の視点では不変のようですが、 Person
新しいサブクラスが定義されているとします。
public class MutablePerson extends Person {
private String newName;
public MutablePerson(String name) {
super(name);
}
@Override
public String getName() {
return newName;
}
public void setName(String name) {
newName = name;
}
}
Person
(im)の変更は、新しいサブクラスを使用して多態性を利用することができます:
Person person = new MutablePerson("Average Joe");
System.out.println(person.getName()); prints Average Joe
person.setName("Magic Mike"); // NOT OK, person has now a new name!
System.out.println(person.getName()); // prints Magic Mike
この問題を解決する、 のいずれかのようにクラスマークするにはfinal
それを拡張することはできませんかのようにそのコンストラクタ(S)のすべての宣言private
。