不変のクラスが必要なシナリオを理解できません。
そのような要件に直面したことがありますか?または、このパターンを使用する必要がある実際の例を教えてください。
不変のクラスが必要なシナリオを理解できません。
そのような要件に直面したことがありますか?または、このパターンを使用する必要がある実際の例を教えてください。
回答:
他の答えは、不変性が優れている理由を説明することに焦点を合わせすぎているようです。とても良いですし、可能な限り使っています。 しかし、それはあなたの質問ではありません。必要な答えと例が確実に得られるように、質問を1つずつ取り上げます。
不変のクラスが必要なシナリオを理解できません。
「必要」はここでは相対的な用語です。不変クラスは、他のパラダイム/パターン/ツールと同様に、ソフトウェアの構築を容易にするために存在するデザインパターンです。同様に、オブジェクト指向パラダイムが登場する前にたくさんのコードが書かれていましたが、オブジェクト指向を「必要とする」プログラマーの中に私を数えてください。OOのような不変のクラスは厳密には必要ありませんが、私はそれらが必要なように行動します。
そのような要件に直面したことがありますか?
問題のあるドメイン内のオブジェクトを正しい視点で見ていない場合、不変オブジェクトの要件が表示されない可能性があります。問題のあるドメインは、いつ有利に使用するかをよく知らない場合、不変のクラスを必要としないと考えるのは簡単かもしれません。
問題のあるドメイン内の特定のオブジェクトを値または固定インスタンスと見なす不変クラスをよく使用します。この概念は、パースペクティブまたはパースペクティブに依存する場合がありますが、理想的には、適切なパースペクティブに切り替えて、適切な候補オブジェクトを識別するのは簡単です。
さまざまな本やオンライン記事を読んで、不変クラスについての考え方をよく理解することで、不変オブジェクトが本当に役立つ場所をよりよく理解できます(厳密に必要ではない場合)。始めるための良い記事の1つは、Javaの理論と実践です。変更するかどうか。
パースペクティブの意味を明確にするために、さまざまなパースペクティブ(可変と不変)でオブジェクトを表示する方法の例をいくつか以下に示します。
...このパターンを使用する実際の例を教えてください。
あなたが実際の例を求めたので、私はあなたにいくつかを与えるでしょう、しかし最初に、いくつかの古典的な例から始めましょう。
クラシック値オブジェクト
文字列と整数は、多くの場合、値と見なされます。したがって、StringクラスとIntegerラッパークラス(および他のラッパークラス)がJavaで不変であることに気付くのは当然のことです。通常、色は値、つまり不変のColorクラスと見なされます。
反例
対照的に、車は通常、価値のあるオブジェクトとは見なされません。車のモデリングとは、通常、状態(走行距離計、速度、燃料レベルなど)が変化するクラスを作成することを意味します。ただし、車が値オブジェクトになる可能性のあるドメインがいくつかあります。たとえば、車(または具体的には車のモデル)は、特定の車両に適切なモーターオイルを検索するためのアプリの値オブジェクトと見なされる場合があります。
トランプ
トランププログラムを書いたことはありますか?やった。トランプを、可変のスーツとランクを持つ可変のオブジェクトとして表すこともできます。ドローポーカーハンドは5つの固定インスタンスである可能性があり、私の手札の5番目のカードを交換すると、スーツとランクのivarを変更して、5番目のトランプインスタンスを新しいカードに変更することになります。
しかし、私はトランプを、一度作成されると固定された不変のスーツとランクを持つ不変のオブジェクトと考える傾向があります。私のドローポーカーハンドは5インスタンスであり、私のハンドのカードを交換するには、それらのインスタンスの1つを破棄し、新しいランダムインスタンスを私のハンドに追加する必要があります。
地図投影法
最後の例は、マップがさまざまな投影法で表示されるマップコードを作成したときです。元のコードでは、マップで固定されているが変更可能なプロジェクションインスタンス(上記の変更可能なトランプなど)が使用されていました。地図投影法を変更するということは、地図の投影インスタンスのivar(投影法の種類、中心点、ズームなど)を変更することを意味します。
ただし、プロジェクションを不変の値または固定インスタンスと考えると、デザインはよりシンプルだと感じました。地図投影法を変更するということは、地図の固定投影法インスタンスを変更するのではなく、地図に別の投影法インスタンスを参照させることを意味しました。これにより、などの名前付きプロジェクションのキャプチャも簡単になりましたMERCATOR_WORLD_VIEW
。
不変クラスは、一般に、正しく設計、実装、および使用するのがはるかに簡単です。例は文字列です。主にその不変性のために、の実装はC ++の実装java.lang.String
よりも大幅に単純ですstd::string
。
不変性が特に大きな違いを生む1つの特定の領域は並行性です。不変オブジェクトは複数のスレッド間で安全に共有できますが、可変オブジェクトは注意深い設計と実装によってスレッドセーフにする必要があります。通常、これは簡単な作業ではありません。
更新: Effective Java 2nd Editionは、この問題に詳細に取り組んでいます-項目15:可変性の最小化を参照してください。
これらの関連記事も参照してください。
Joshua Blochによる効果的なJavaは、不変のクラスを作成するいくつかの理由を概説しています。
一般に、結果として深刻なパフォーマンスの問題が発生しない限り、オブジェクトを不変にすることをお勧めします。このような状況では、可変ビルダーオブジェクトを使用して、StringBuilderなどの不変オブジェクトを構築できます。
ハッシュマップは典型的な例です。マップのキーは不変であることが不可欠です。キーが不変ではなく、hashCode()が新しい値になるようにキーの値を変更すると、マップが壊れます(キーがハッシュテーブルの間違った場所にあるようになりました)。
Javaは事実上すべての参照です。インスタンスが複数回参照される場合があります。このようなインスタンスを変更すると、そのすべての参照に反映されます。堅牢性とスレッドセーフを向上させるために、これが必要ない場合もあります。次に、不変のクラスが役立つので、新しいインスタンスを作成して現在の参照に再割り当てする必要があります。このようにして、他の参照の元のインスタンスは変更されません。
String
変更可能である場合、Javaがどのように見えるか想像してみてください。
Date
とCalendar
が変更可能だった場合。ああ、待って、彼らは、OH SH
String
は、変更可能です。(ヒント:いくつかの古いJRockitバージョン)。string.trim()を呼び出すと、元の文字列がトリミングされました
不変のクラス自体は必要ありませんが、特に複数のスレッドが関係している場合は、プログラミングタスクを簡単にすることができます。不変オブジェクトにアクセスするためにロックを実行する必要はありません。そのようなオブジェクトについてすでに確立している事実は、今後も当てはまります。
極端な場合を考えてみましょう:整数定数。「x = x + 1」のようなステートメントを書く場合、プログラムの他の場所で何が起こっても、「1」という数字がどういうわけか2にならないことを100%確信したいと思います。
さて、整数定数はクラスではありませんが、概念は同じです。私が書いたとしましょう:
String customerId=getCustomerId();
String customerName=getCustomerName(customerId);
String customerBalance=getCustomerBalance(customerid);
簡単そうに見えます。ただし、文字列が不変でない場合は、getCustomerNameがcustomerIdを変更する可能性を考慮する必要があります。これにより、getCustomerBalanceを呼び出すと、別の顧客の残高が得られます。「なぜ、getCustomerName関数を作成すると、IDが変更されるのでしょうか?それは意味がありません」と言うかもしれません。しかし、それはまさにあなたが問題を起こす可能性がある場所です。上記のコードを書いている人は、関数がパラメーターを変更しないことは明らかだと思うかもしれません。次に、顧客が同じ名前で複数のアカウントを持っている場合を処理するために、その関数の別の使用法を変更する必要がある誰かがやって来ます。そして彼は、「ああ、これはすでに名前を検索しているこの便利なgetCustomername関数です。私は」と言います。
不変性とは、特定のクラスのオブジェクトが定数であることを意味し、それらを定数として扱うことができます。
(もちろん、ユーザーは別の「定数オブジェクト」を変数に割り当てることができます。誰かがString s = "hello"を記述し、後でs = "goodbye"を記述できます。変数をfinalにしない限り、確信が持てません。自分のコードブロック内で変更されていないこと。整数定数と同じように、「1」は常に同じ数ですが、「x = 1」を「x = 2」と書くことによって変更されることはありません。不変オブジェクトへのハンドルがある場合、それを渡す関数がそれを変更できないこと、またはオブジェクトのコピーを2つ作成した場合、1つのコピーを保持する変数を変更しても変更されないことを確信できます。その他。
不変性にはさまざまな理由があります。
String
クラス。したがって、ネットワークサービスを介してデータを送信する場合で、送信した結果とまったく同じ結果が得られるという保証が必要な場合は、不変として設定してください。
final
Javaでフラグが立てられたすべてのクラスが不変であるわけではなく、すべての不変クラスがフラグが立てられてfinal
いるわけではありません。
これを別の視点から攻撃します。不変のオブジェクトを使用すると、コードを読むときに作業が楽になります。
可変オブジェクトがある場合、それが私の直接の範囲外で使用された場合、その値が何であるかはわかりません。MyMutableObject
メソッドのローカル変数で作成し、値を入力してから、他の5つのメソッドに渡すとします。これらのメソッドのいずれかがオブジェクトの状態を変更する可能性があるため、次の2つのいずれかが発生する必要があります。
最初のものは私のコードについての推論を難しくします。2つ目は、コードのパフォーマンスを低下させます。基本的には、コピーオンライトセマンティクスを使用して不変オブジェクトを模倣していますが、呼び出されたメソッドが実際にオブジェクトの状態を変更するかどうかに関係なく、常に実行しています。
代わりにを使用する場合MyImmutableObject
、設定するのは、メソッドの存続期間中の値であると確信できます。私の下からそれを変える「遠隔作用」はなく、他の5つのメソッドを呼び出す前にオブジェクトの防御コピーを作成する必要もありません。他のメソッドが目的のために物事を変更したい場合は、コピーを作成する必要がありますが、実際にコピーを作成する必要がある場合にのみこれを行います(すべての外部メソッド呼び出しの前に行うのとは対照的です)。現在のソースファイルに含まれていない可能性のあるメソッドを追跡するという精神的なリソースを節約し、万が一の場合に備えて、システムに不要な防御コピーを際限なく作成するオーバーヘッドを節約します。
(Javaの世界の外に出て、たとえばC ++の世界に入ると、さらにトリッキーになる可能性があります。オブジェクトを変更可能であるかのように見せることはできますが、舞台裏では、オブジェクトを透過的に複製します。ある種の状態変化、つまりコピーオンライトであり、誰も賢くはありません。)
将来の訪問者のための私の2セント:
不変オブジェクトが適切な選択である2つのシナリオは次のとおりです。
マルチスレッド環境での同時実行の問題は同期によって非常によく解決できますが、同期にはコストがかかるため(ここでは「理由」については掘り下げません)、不変オブジェクトを使用している場合、同時実行の問題を解決するための同期はありません。不変オブジェクトは変更できません。状態を変更できない場合は、すべてのスレッドがオブジェクトにシームレスにアクセスできます。したがって、不変オブジェクトは、マルチスレッド環境の共有オブジェクトに最適です。
ハッシュベースのコレクションを操作するときに注意する最も重要なことの1つは、キーはhashCode()
オブジェクトの存続期間中常に同じ値を返すようにする必要があるということです。その値が変更されると、ハッシュベースのコレクションに古いエントリが作成されるためです。そのオブジェクトを使用すると取得できないため、メモリリークが発生します。不変オブジェクトの状態は変更できないため、ハッシュベースのコレクションのキーとして最適です。したがって、ハッシュベースのコレクションのキーとして不変オブジェクトを使用している場合は、そのためにメモリリークが発生しないことを確認できます(もちろん、キーとして使用されているオブジェクトがどこからも参照されていない場合でも、メモリリークが発生する可能性があります)それ以外の場合、しかしそれはここでのポイントではありません)。
不変オブジェクトは、一度開始されると状態が変化しないインスタンスです。このようなオブジェクトの使用は、要件固有です。
不変クラスはキャッシュの目的に適しており、スレッドセーフです。
不変性のおかげで、基礎となる不変オブジェクトの動作/状態が変化しないことを確認でき、追加の操作を実行するという追加の利点が得られます。
複数のコア/処理(並行/並列処理)を簡単に使用できます(操作の順序は重要ではなくなります)。
コストのかかる操作のキャッシュを実行できます(同じ
結果が確実に得られるため)。
デバッグを簡単に行うことができます(実行の履歴は
もう問題にならないため)
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」キーワードがプログラマーのエラーを防ぐためにあることを示すための単なる例であり、それ以上のものではありません。最後のキーワードがない値の再割り当ては偶然に簡単に発生する可能性がありますが、値を変更するためにこの長さにすることは意図的に行う必要があります。ドキュメントとプログラマーのエラーを防ぐためにあります。
不変のデータ構造は、再帰的アルゴリズムをコーディングするときにも役立ちます。たとえば、3SAT問題を解決しようとしているとします。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()
aconst std::string&
を返すと、参照によって返され、ミューテーターへのアクセスが防止されます。つまり、そのコンテキストでは不変のクラスは必要ありません。
彼らはまた私達に保証を与えます。不変性の保証は、それらを拡張し、他の方法では不可能な効率のための新しいパターンを作成できることを意味します。
まだ呼び出されていない不変クラスの1つの機能:深く不変のクラスオブジェクトへの参照を格納することは、そこに含まれるすべての状態を格納する効率的な手段です。深く不変のオブジェクトを使用して50K相当の状態情報を保持する可変オブジェクトがあるとします。さらに、25回、元の(変更可能な)オブジェクトの「コピー」を作成したいとします(たとえば、「元に戻す」バッファの場合)。状態はコピー操作間で変化する可能性がありますが、通常は変化しません。可変オブジェクトの「コピー」を作成するには、参照をその不変状態にコピーするだけでよいため、20個のコピーは単純に20個の参照になります。対照的に、状態が50K相当の可変オブジェクトで保持されている場合、25回のコピー操作のそれぞれで50K相当のデータの独自のコピーを生成する必要があります。25部すべてを保持するには、1メガ分のほとんど複製されたデータを保持する必要があります。最初のコピー操作は決して変更されないデータのコピーを生成し、他の24の操作は理論的には単にそれを参照することができますが、ほとんどの実装では、2番目のオブジェクトがコピーを要求する方法はありません。不変のコピーがすでに存在することを知るための情報(*)。
(*)時々役立つパターンの1つは、可変オブジェクトがその状態を保持する2つのフィールドを持つことです。1つは可変形式で、もう1つは不変形式です。オブジェクトは、可変または不変としてコピーでき、いずれかの参照セットから始まります。オブジェクトが状態を変更しようとするとすぐに、不変の参照を可変の参照にコピーし(まだ行われていない場合)、不変の参照を無効にします。オブジェクトが不変としてコピーされるときに、その不変の参照が設定されていない場合、不変のコピーが作成され、不変の参照がそれを指します。このアプローチでは、「書き込み時の本格的なコピー」よりもいくつかのコピー操作が必要になります(たとえば、最後のコピー以降に変更されたオブジェクトのコピーを要求すると、コピー操作が必要になります。
効果的なJavaから; 不変クラスは、インスタンスを変更できないクラスです。各インスタンスに含まれるすべての情報は、インスタンスが作成されたときに提供され、オブジェクトの存続期間中固定されます。Javaプラットフォームライブラリには、String、ボックス化されたプリミティブクラス、BigIntegerおよびBigDecimalなど、多くの不変クラスが含まれています。これには多くの理由があります。不変クラスは、可変クラスよりも設計、実装、および使用が簡単です。エラーが発生しにくく、安全性が高くなります。