これは重要な質問です。基本的に、ゲッターを介して、または別のクラスのセッターを呼び出して、他のクラスに与えるクラスの内部状態について考える必要があります。たとえば、次のようにします。
Date now = new Date();
someObject.setDate(now);
// another use of "now" that expects its value to not have changed
次に、2 つの問題が発生する可能性があります。
someObject
" " の値を変更する可能性がありますnow
。つまり、後でその変数を使用するときに、上記のメソッドが予想とは異なる値を持つ可能性があります。
now
" " を渡した後でsomeObject
その値を変更しsomeObject
、防御的なコピーを作成しなかった場合は、 の内部状態を変更したことになりますsomeObject
。
コードのクライアントが誰であるかに応じて、両方のケースから保護するか、何が許可され、何が許可されないかを文書化する必要があります。もう 1 つのケースは、クラスに があり、それ自体Map
にゲッターを提供する場合ですMap
。がオブジェクトMap
の内部状態の一部であり、オブジェクトが の内容を完全に管理することを期待している場合はMap
、 を決して外に出してはなりませんMap
。マップに getter を提供する必要がある場合は、Collections.unmodifiableMap(myMap)
代わりに を返しますmyMap
。ここでは、コストがかかる可能性があるため、おそらくクローンまたは防御コピーを作成したくないでしょう。Map を変更できないようにラップして返すことで、内部状態が別のクラスによって変更されるのを防ぎます。
多くの理由で、clone()
多くの場合、適切なソリューションではありません。いくつかのより良い解決策は次のとおりです。
- ゲッターの場合:
- a を返す代わりに、クライアント コードが必要なことを実行できるようにするものに、 または に
Map
のみIterator
s を返します。つまり、本質的に内部状態の読み取り専用ビューである何かを返すか、またはkeySet
Map.Entry
- 次のような不変のラッパーにラップされた可変状態オブジェクトを返します
Collections.unmodifiableMap()
- を返すのではなく、キーを受け取り、マップから対応する値を返すメソッドを
Map
提供します。get
すべてのクライアントがそれを使用して値を取得する場合は、クライアントにそれ自体Map
を提供しないでください。代わりに、のメソッドMap
をラップする getter を提供してください。Map
get()
- コンストラクターの場合:
- オブジェクトコンストラクターでコピーコンストラクターを使用して、渡された変更可能なもののコピーを作成します。
- 変更可能な量ではなく、可能な場合はコンストラクターの引数として不変の量を取るように設計します。
new Date().getTime()
オブジェクトではなく、たとえばによって返される long を取得することが理にかなっている場合がありDate
ます。
- できるだけ多くの状態を作成しますが、オブジェクトは変更可能であり、配列は変更可能であること
final
を忘れないでください。final
final
どのような場合でも、誰が可変状態を「所有」しているかについて疑問がある場合は、ゲッター、セッター、またはコンストラクターについて文書化してください。どこかに文書化してください。
悪いコードの簡単な例を次に示します。
import java.util.Date;
public class Test {
public static void main(String[] args) {
Date now = new Date();
Thread t1 = new Thread(new MyRunnable(now, 500));
t1.start();
try { Thread.sleep(250); } catch (InterruptedException e) { }
now.setTime(new Date().getTime()); // BAD! Mutating our Date!
Thread t2 = new Thread(new MyRunnable(now, 500));
t2.start();
}
static public class MyRunnable implements Runnable {
private final Date date;
private final int count;
public MyRunnable(final Date date, final int count) {
this.date = date;
this.count = count;
}
public void run() {
try { Thread.sleep(count); } catch (InterruptedException e) { }
long time = new Date().getTime() - date.getTime();
System.out.println("Runtime = " + time);
}
}
}
各ランナブルが 500 ミリ秒スリープするはずですが、代わりに間違った時間情報が取得されます。コンストラクターを変更して防御的なコピーを作成する場合:
public MyRunnable(final Date date, final int count) {
this.date = new Date(date.getTime());
this.count = count;
}
その後、正しい時刻情報を取得します。これは簡単な例です。複雑な例をデバッグする必要はありません。
注:状態を適切に管理できなかった場合の一般的なConcurrentModificationException
結果は、コレクションを反復処理する場合です。
防御的にコーディングする必要がありますか? エキスパートプログラマーの同じ小さなチームが常にプロジェクトの作成と保守を行うこと、プロジェクトの詳細の記憶を保持するために継続的に作業すること、同じ人々がプロジェクトのために作業することを保証できる場合.プロジェクトの存続期間が短く、プロジェクトが「大規模」になることは決してないということであれば、そうしなくても済むかもしれません。しかし、最もまれなケースを除いて、防御的プログラミングのコストは大きくなく、メリットは大きいです。さらに、防御的なコーディングは良い習慣です。変更可能なデータを、それを持ってはいけない場所に渡すという悪い習慣の開発を助長したくはありません。これはいつかあなたをかみます。もちろん、これはすべて、プロジェクトに必要な稼働時間によって異なります。