不変のクラスが必要なシナリオを取得できません。
そのような要件に直面したことはありますか?または、このパターンを使用する実際の例を教えてください。
一般に、不変クラスは設計、実装、および正しく使用するのがはるかに簡単です。例は文字列です:Java.lang.String
の実装は、主に不変性のため、C++のstd::string
の実装よりも大幅に単純です。
不変性が特に大きな違いをもたらす特定の領域の1つは同時実行性です:不変オブジェクトは複数のスレッド間で安全に共有できますが、可変オブジェクトはスレッドセーフにする必要があります慎重な設計と実装を介して-通常、これは簡単な作業にはほど遠いです。
Update:Effective Java 2nd Edition この問題に詳細に取り組む-項目15:可変性を最小限に抑える。
これらの関連記事も参照してください。
Effective Java by Joshua Blochは、不変クラスを記述するいくつかの理由を概説しています。
一般に、結果として深刻なパフォーマンスの問題がない限り、オブジェクトを不変にすることをお勧めします。このような状況では、可変ビルダーオブジェクトを使用して不変オブジェクトを構築できます。 StringBuilder
ハッシュマップは典型的な例です。マップのキーは不変であることが不可欠です。キーが不変ではなく、hashCode()が新しい値になるようにキーの値を変更すると、マップが破損します(キーはハッシュテーブルの間違った場所にあります)。
Javaは実質的に1つのすべての参照です。インスタンスが複数回参照される場合があります。このようなインスタンスを変更すると、すべての参照に反映されます。堅牢性とスレッドセーフを向上させるために、単にこれを使いたくない場合があります。次に、不変クラスが有用であるため、newインスタンスを作成し、現在の参照に再割り当てする必要があります。このようにして、他の参照の元のインスタンスはそのまま残ります。
String
が可変であれば、Javaがどのように見えるかを想像してください。
need不変クラス自体はありませんが、特に複数のスレッドが関係している場合は、プログラミングタスクを簡単にすることができます。不変オブジェクトにアクセスするためにロックを実行する必要はありません。そのようなオブジェクトについてすでに確立した事実は、今後も引き続き真実になります。
これを別の視点から攻撃します。不変のオブジェクトは、コードを読むときの生活を楽にしてくれます。
可変オブジェクトがある場合、それが私の直接のスコープ外で使用された場合、その値が何であるかは決してわかりません。メソッドのローカル変数にMyMutableObject
を作成し、値を入力して、他の5つのメソッドに渡します。これらのメソッドのいずれかがオブジェクトの状態を変更する可能性があるため、次の2つのいずれかを実行する必要があります。
最初のものは私のコードについての推論を難しくします。 2番目はコードのパフォーマンスを低下させます-基本的にはコピーオンライトセマンティクスで不変オブジェクトを模倣しますが、呼び出されたメソッドが実際にオブジェクトの状態を変更するかどうかにかかわらず、常にそれを行います。
代わりにMyImmutableObject
を使用する場合、設定した値がメソッドの存続期間の値になることを保証できます。私の下からそれを変える「遠くでの不気味な行動」はなく、他の5つのメソッドを呼び出す前にオブジェクトの防御的なコピーを作成する必要はありません。他のメソッドが目的のために物事を変更したい場合theyはコピーを作成する必要がありますが、実際にコピーを作成する必要がある場合にのみこれを行いますメソッド呼び出し)。現在のソースファイルにはないメソッドを追跡する精神的なリソースを節約し、万が一に備えて不必要な防御コピーを作成するというオーバーヘッドをシステムに与えています。
(もしJavaの世界からC++の世界に行くと、とりわけC++の世界に入ると、さらにトリッキーになります。オブジェクトを可変のように見せることができますが、舞台裏では、誰も賢明な状態ではなく、あらゆる種類の状態変更(コピーオンライト)で透過的にクローンを作成します。
極端なケースを見てみましょう:整数定数。 「x = x + 1」のようなステートメントを記述する場合、プログラムの他の場所で何が起こっても、「1」がどういうわけか2にならないことを100%確信します。
さて、整数定数はクラスではありませんが、概念は同じです。私が書くと仮定します:
String customerId=getCustomerId();
String customerName=getCustomerName(customerId);
String customerBalance=getCustomerBalance(customerid);
とてもシンプルに見えます。ただし、文字列が不変でない場合は、getCustomerNameがcustomerIdを変更する可能性を考慮する必要があるため、getCustomerBalanceを呼び出すと、別の顧客の残高が取得されます。 「世界でgetCustomerName関数を書いている人がidを変更するのはなぜですか?それは意味がありません」と言うかもしれません。しかし、それはまさにあなたがトラブルに巻き込まれる可能性がある場所です。上記のコードを書いている人は、関数がパラメーターを変更しないことは明らかであると考えるかもしれません。次に、顧客が同じ名前で複数のアカウントを持っている場合を処理するために、その関数の別の使用を変更する必要がある誰かがやって来ます。そして、彼は「ああ、これはすでに名前を検索しているこの便利なgetCustomer名前関数です。IDを同じ名前の次のアカウントに自動的に変更し、それをループに入れます...」そしてそれからあなたのプログラムは不思議なことに動き始めます。それは悪いコーディングスタイルでしょうか?多分。しかし、副作用が明らかでない場合、それはまさに問題です。
不変性とは、オブジェクトの特定のクラスが定数であることを意味し、それらを定数として扱うことができます。
(もちろん、ユーザーは別の「定数オブジェクト」を変数に割り当てることができます。誰かがString s = "hello"を記述し、後でs = "goodbye"を記述できます;変数をfinalにしない限り、確信が持てません。整数定数と同じように、「1」は常に同じ数であることを保証しますが、「x = 1」は「x = 2」を書いても決して変更されないことを保証します。不変オブジェクトへのハンドルを持っている場合、渡す関数がそれを変更できないこと、またはそのコピーを2つ作成する場合、1つのコピーを保持する変数を変更しても、その他。
不変性にはさまざまな理由があります。
String
クラス。したがって、ネットワークサービス経由でデータを送信する場合で、送信した結果とまったく同じ結果が得られるという保証の感覚が必要な場合は、不変として設定します。
将来の訪問者のための私の2セント:
不変オブジェクトが適切な2つのシナリオは次のとおりです。
マルチスレッド環境での同時実行性の問題は同期によって非常によく解決できますが、同期はコストのかかる問題です(ここでは「理由」については掘り下げません)。したがって、不変オブジェクトを使用している場合、不変オブジェクトは変更できず、状態を変更できない場合は、すべてのスレッドがオブジェクトにシームレスにアクセスできます。 つまり、不変オブジェクトは、マルチスレッド環境の共有オブジェクトに最適です。
ハッシュベースのコレクションで作業する際に注意すべき最も重要なことの1つは、そのhashCode()
がオブジェクトのライフタイムに対して常に同じ値を返すようなキーであることです。そのオブジェクトを使用してハッシュベースのコレクションに作成されたエントリは取得できないため、メモリリークが発生します。 不変オブジェクトの状態は変更できないため、ハッシュベースのコレクションのキーとして最適な選択になります。したがって、不変オブジェクトをハッシュベースのコレクションのキーとして使用している場合、そのため、メモリリークではありません(もちろん、キーとして使用されるオブジェクトが他のどこからも参照されていない場合でも、メモリリークが発生する可能性がありますが、それはここでのポイントではありません)。
不変データ構造は、再帰アルゴリズムをコーディングするときにも役立ちます。たとえば、 SAT 問題を解決しようとしているとしましょう。 1つの方法は、次のことを行うことです。
問題を表す可変構造がある場合、TRUEブランチのインスタンスを単純化するとき、次のいずれかを行う必要があります。
ただし、巧妙な方法でコーディングすれば、不変の構造を持つことができます。この構造では、どの操作でも問題の更新された(ただし不変の)バージョンが返されます(String.replace
-文字列を置き換えず、新しい文字列を提供します)。これを実装する素朴な方法は、「不変」構造を変更時にコピーして新しいものを作成することです。変更可能なものがある場合は2番目のソリューションに減らし、オーバーヘッドがすべてありますが、効率的な方法。
不変クラスの「必要性」の理由の1つは、すべてを参照で渡すことと、オブジェクトの読み取り専用ビュー(つまり、C++のconst
)をサポートしないことの組み合わせです。
オブザーバーパターンをサポートするクラスの単純なケースを考えてみましょう。
_class Person {
public string getName() { ... }
public void registerForNameChange(NameChangedObserver o) { ... }
}
_
string
が不変でない場合、Person
クラスがregisterForNameChange()
を正しく実装することは不可能です。誰かが次のように書くことができるためです。お知らせ。
_void foo(Person p) {
p.getName().prepend("Mr. ");
}
_
C++では、getName()
が_const std::string&
_を返すと、参照によって戻り、ミューテーターへのアクセスを防ぐ効果があります。つまり、そのコンテキストでは不変クラスは不要です。
Finalキーワードを使用しても、必ずしも何かが不変になるわけではありません。
public class Scratchpad {
public static void main(String[] args) throws Exception {
SomeData sd = new SomeData("foo");
System.out.println(sd.data); //prints "foo"
voodoo(sd, "data", "bar");
System.out.println(sd.data); //prints "bar"
}
private static void voodoo(Object obj, String fieldName, Object value) throws Exception {
Field f = SomeData.class.getDeclaredField("data");
f.setAccessible(true);
Field modifiers = Field.class.getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(f, f.getModifiers() & ~Modifier.FINAL);
f.set(obj, "bar");
}
}
class SomeData {
final String data;
SomeData(String data) {
this.data = data;
}
}
「final」キーワードは、プログラマーのエラーを防ぐためだけでなく、それ以上ではないことを示すための単なる例です。最終的なキーワードのない値の再割り当ては偶然に簡単に発生する可能性がありますが、この長さで値を変更することは意図的に行う必要があります。文書化のため、およびプログラマーのエラーを防ぐためにあります。
彼らはまた私達に保証を与えます。不変性の保証とは、それらを拡張し、それ以外では不可能な効率のために新しいパターンを作成できることを意味します。
不変オブジェクトは、開始された後に状態が変化しないインスタンスです。このようなオブジェクトの使用は要件に固有です。
不変クラスは、キャッシュの目的に適しており、スレッドセーフです。
まだ呼び出されていない不変クラスの1つの機能:深く不変のクラスオブジェクトへの参照を格納することは、そこに含まれるすべての状態を格納する効率的な手段です。深く不変のオブジェクトを使用して50Kの状態情報を保持する可変オブジェクトがあるとします。さらに、25回にわたって、元の(可変)オブジェクトの「コピー」を作成するとします(たとえば、「元に戻す」バッファー用)。コピー操作間で状態が変わる可能性がありますが、通常は変わりません。可変オブジェクトの「コピー」を作成するには、その不変状態への参照をコピーするだけでよいため、20個のコピーは単に20個の参照になります。対照的に、状態が50K相当の可変オブジェクトに保持されている場合、25のコピー操作のそれぞれは、50K相当のデータの独自のコピーを作成する必要があります。 25個すべてのコピーを保持するには、Meg相当のほとんど複製されたデータを保持する必要があります。最初のコピー操作は変更されないデータのコピーを生成し、他の24の操作は理論的には単純にそれを参照できますが、ほとんどの実装では、2番目のオブジェクトがコピーを要求する方法はありません不変のコピーが既に存在することを知るための情報(*)。
(*)時々役立つパターンの1つは、可変オブジェクトに状態を保持する2つのフィールドがあることです。1つは可変形式で、もう1つは不変形式です。オブジェクトは、可変または不変としてコピーでき、いずれかの参照セットで開始されます。オブジェクトが状態を変更するとすぐに、不変の参照を変更可能な参照にコピーし(まだ実行されていない場合)、不変の参照を無効にします。オブジェクトが不変としてコピーされ、その不変参照が設定されていない場合、不変コピーが作成され、不変参照がそれを指します。このアプローチでは、「書き込み時の本格的なコピー」よりもいくつかのコピー操作が必要になります(元のオブジェクトが再び変更されない場合でも、最後のコピーがコピー操作を必要とするために変更されたオブジェクトをコピーするように要求するなど) )しかし、FFCOWが伴うスレッド化の複雑さを回避します。
なぜ不変クラスなのか?
オブジェクトがインスタンス化されると、ライフタイム中に状態を変更することはできません。また、これによりスレッドセーフになります。
例:
明らかに、String、Integer、BigDecimalなど。これらの値が作成されると、ライフタイム中に変更することはできません。
ユースケース:データベース接続オブジェクトが構成値で作成されると、不変クラスを使用できる状態を変更する必要がなくなる場合があります
効果的なJavaから。不変クラスは、インスタンスを変更できないクラスです。各インスタンスに含まれるすべての情報は、作成時に提供され、オブジェクトの存続期間中は固定されます。 Javaプラットフォームライブラリには、文字列、ボックス化されたプリミティブクラス、BigIntegerおよびBigDecimalを含む多くの不変クラスが含まれています。これには多くの理由があります。可変クラスよりも使用します。エラーが発生しにくく、安全です。
不変性のおかげで、基礎となる不変オブジェクトの動作/状態が変わらないことを確認でき、追加の操作を実行する利点が追加されます。
複数のコア/処理(同時/並列処理)を簡単に使用できます(操作の順序は問題ではないため)
キャッシングできる
結果)。
デバッグを簡単に実行できます(実行の履歴が問題にならないため)
もう)