この質問は、優れたプログラミング手法と潜在的な穴の回避に関するものです。
JoshuaBlochのEffectiveJavaを読みましたが、これが疑問
です。ミューテーターを含まない不変クラスのgetterメソッドで防御コピーを作成することを検討する必要があるのはなぜですか。
そして第二に:なぜ私は自分のフィールドをプライベートに加えてファイナルにする必要があるのですか?これはパフォーマンスのみに関するものですか(セキュリティではありません)?
7 に答える
私はこれがこの声明を正当化するケースであると信じています:
public class Immutable {
private final String name;
private Date dateOfBirth;
public Immutable(String name, Date dateOfBirth) {
this.name = name;
this.dateOfBirth = dateOfBirth;
}
public String getName() {
return name;
}
public Date getDateOfBirth() {
return dateOfBirth;
}
}
getName()
不変オブジェクトも返すので問題ありません。ただし、getDateOfBirth()
クライアントコードは返されたオブジェクトを変更できるため、メソッドは不変性を破ることができます。したがって、Immutable
オブジェクトも変更されます。
Immutable imm = new Immutable("John", new Date());
imm.getName(); //safe
Date dateOfBirth = imm.getDateOfBirth();
//hundreds of lines later
dateOfBirth.setTime(0); //we just modified `imm` object
不変のオブジェクトとプリミティブを返すのは安全です(値によって返されるため)。ただし、次のような可変オブジェクトの防御コピーを作成する必要がありますDate
。
public Date getDateOfBirth() {
return new Date(dateOfBirth.getTime());
}
コレクションを不変のビューでラップします(可変の場合)。例:以下を参照してくださいCollections.unmodifiableList()
。
public List<Integer> getSomeIds() {
return Collections.unmodifiableList(someIds);
}
Javaでのオブジェクト参照は無差別です。オブジェクト参照がメソッドに渡された場合、そのメソッドには、他に誰が同じオブジェクトへの参照を持っているか、または同様にオブジェクトで何をしようとしているのかを知る方法がありません。同様に、メソッドがオブジェクト参照を返す場合、受信者がそれを使って何をする可能性があるかはわかりません。したがって、型が参照を持つすべての人にそれを変更できる場合、そのようなオブジェクトへの参照を保持するエンティティがそれが変更されないことを保証できる唯一の方法は、外部が持っていなかったプライベートオブジェクトへの参照を保持することです、への参照を取得することはありません。
ここに示す防御コピーのスタイル(情報が要求されるたびに新しいオブジェクトインスタンスを構築する)の代わりに、オブジェクトの情報取得メソッドで変更可能なオブジェクトを受け入れ、取得した情報をそのオブジェクトに入力することもできます。このアプローチでは、情報を要求するコードが、情報を取得するメソッドを呼び出す前に、情報を受け入れる(空白の可能性が高い)オブジェクトを構築する必要がありますが、たとえば、ループを使用して取得し、簡単に調べて、100個の情報を破棄する場合情報については、ループ全体で毎回再利用できる1つのオブジェクトを作成する方が、それぞれが短時間しか使用されない100個の新しいオブジェクトを作成するよりも高速な場合があります。
クライアントがオブジェクトの状態を変更する可能性があるため、ゲッターに返すオブジェクトが変更可能である場合にのみ、防御コピーを実行する必要があります。
最後の質問に関しては、フィールドを最終的にする必要は厳密にはありませんが、オブジェクトが作成されると変更できない最終的な付与にします。
実際、オブジェクトの作成後にオブジェクトの一部のフィールドを変更する必要がある場合、これも問題ありませんが、クライアントコードが、オブジェクトの状態が変更されたことを識別できないことを確認する必要があります。不変にする必要があるのは、内部状態ではなく、オブジェクトの外部の可視状態です。
たとえば、Stringクラスは、作成時にハッシュコードを計算せず、最初に必要になったときにハッシュコードを計算してから、プライベートの可変フィールドにキャッシュします。
私はあなたのクラスがfinalとして宣言されているか、プライベートコンストラクターしかないことを前提としています。そうしないと、そのサブクラスが予測できない方法で非finalフィールドを変更する可能性があります...
明確にするためのいくつかのサンプルコード:
public final class Question { //final class to assure that inheritor could not
// make it mutable
private int textLenCache = -1; //we alter it after creation, only if needed
private final String text;
private Date createdOn;
public Immutable(String text, Date createdOn) {
Date copy=new Date(createdOn.getTime() ) //defensive copy on object creation
//Ensure your class invariants, e.g. both params must be non null
//note that you must check on defensive copied object, otherwise client
//could alter them after you check.
if (text==null) throw new IllegalArgumentException("text");
if (copy==null) throw new IllegalArgumentException("createdOn");
this.text= text; //no need to do defensive copy an immutable object
this.createdOn= copy;
}
public int getTextLen() {
if (textLenCache == -1)
textLenCache=text.length(); //client can't see our changed state,
//this is fine and your class is still
//immutable
return textLenCache;
}
public Date getCreatedOn() {
return new Date(createdOn.getTime()); //Date is mutable, so defend!
}
}
編集:
コンストラクターの可変パラメーターの防御コピーは、次の2つの理由で必要です。
クライアントコードは、オブジェクトの作成後にパラメータの状態を変更する可能性があります。この可能性を回避するには、パラメータのコピーを作成する必要があります。たとえば、Stringオブジェクトコンストラクター String(char [] value)が、指定したchar配列をコピーせずに使用した場合、コンストラクターで指定したchar配列を変更することでStringの内容を変更できます。
可変オブジェクトの状態が、制約をチェックしてからフィールドにコピーするまでの間に変化しないようにする必要があります。この理由から、パラメータのローカルコピーの制約を常にチェックする必要があります。
囲んでいるクラスは不変である可能性がありますが、そのgetterメソッドによって返される参照は変更可能である可能性があり、呼び出し元は不変の推移的な状態を変更できます。例:
public class MyImmutable
{
private final StringBuilder foo = new StringBuilder("bar");
public StringBuilder getFoo()
{
return foo; // BAD!
}
}
カプセル化にはprivateを使用する必要があります(クラスがクラスの実装の詳細に依存しないようにします)。フィールドが変更されることを意図していない場合は、フィールドを誤って変更しないように、finalを使用する必要があります(そうです、パフォーマンスに役立つ可能性があります)。
Tomasz Nurkiewiczの回答を少し変更して、dateOfBirthをfinalにすると、クライアントクラスによる変更を防ぐことができない理由を示します。
public class Immutable {
private final String name;
private final Date dateOfBirth;
public Immutable(String name, Date dateOfBirth) {
this.name = name;
this.dateOfBirth = dateOfBirth;
}
public String getName() { return name; }
public Date getDateOfBirth() { return dateOfBirth; }
public static void main(String[] args) {
//create an object
Immutable imm = new Immutable("John", new Date());
System.out.println(imm.getName() + " " + imm.getDateOfBirth());
//try and modify object's intenal
String name = imm.getName(); //safe because Integer is immutable
name = "George"; //has no effect on imm
Date dateOfBirth = imm.getDateOfBirth();
dateOfBirth.setTime(0); //we just modified `imm` object
System.out.println(imm.getName() + " " + imm.getDateOfBirth());
}
}
**Output:**
John Wed Jan 13 11:23:49 IST 2016
John Thu Jan 01 02:00:00 IST 1970
ミューテーターを含まない不変クラスのgetterメソッドで防御コピーを作成することを検討する必要があるのはなぜですか?
これは、返されたオブジェクトがまだ不変でない場合にのみ役立ちます。
このような深い不変性は、不変クラスによって保持されているオブジェクトの変更を防ぐのに役立ちます。
オブジェクトのキャッシュがあると考えてください。オブジェクトがキャッシュから取得されて変更されると、キャッシュ内の値も変更されるリスクがあります。
プライベートに加えて自分のフィールドをファイナルにする必要があるのはなぜですか?
単に不変性を実現し、一度設定すると(たとえば、サブクラス化によって)値が誤ってまたは意図的に変更されるのを防ぐためです。
1防御的なコピーを作成しない場合は、オブジェクトを配布するだけで済みます。一度配布されると、クラスは不変ではなくなり、呼び出し元はオブジェクトを自由に変更できます。
public class Immutable {
private List<Object> something;
public List<Object> getSomething(){
return something; // everything goes for a toss, once caller has this, it can be changed
}
}
2フィールドがプライベートで最終的でない場合は、フィールドを再初期化できることを意味しますが、フィールドが最終的である場合は、複数回ではなく1回だけ初期化され、不変性を実現します。