web-dev-qa-db-ja.com

可変クラスと不変クラスの違いを理解する

私はこの質問に1つのインタビューで直面しました。 Stringは不変であり、StringBufferは可変クラスであることを説明しました。可変と不変についてはあまり知りませんし、正確な答えもわかりません。 2つのアイデアを区別するための重要な概念は何ですか?どちらか一方を選択すると、パフォーマンスに影響がありますか?

5
prakash

不変クラスはimmutableであり、変更不可能です。変更することはできません。 x :: Stringがある場合、xが決して変更されないことを100%確信できます(または、少なくとも変更される場合、人々はあなたの言語の柔軟性を乱用しており、どこに行くべきかを伝える権利があります)。

変更可能なクラスは不変ではありません。あなたはそれを変えることができ、他の人がそれを変えることができます、そしてあなたがそれが同じであることに依存することはできません。別のスレッドで変更される可能性があり、適切なロックがなければ、行ごとに同じ状態にあるかどうかさえ確信できません。

オブジェクトの状態を変更する機能は重要な概念ですが、ストーリーはそこで終わりません。スレッド化された環境で不変クラスを選択することをお勧めします。これは、ロックを心配する必要がないためです。とにかく誰もクラスに書き込むことができないからです。大規模なシステムでは、不変オブジェクトを選択することをお勧めします。これは、他の誰もその入力パラメーターにハンドルを持っていることを確認できず、最初の機会であなたの下から変更されないためです。不変オブジェクトを選択すると、小さな単位でコードの動作を推論できるようになる場合があります。たとえば、関数に関するすべてのものが不変データに依存している場合、その関数を分離して確認できます。

パフォーマンスへの影響はありますが、すべてが一方向というわけではありません。不変オブジェクトを使用すると、メモリを積極的に共有したり(変更できないため)、より積極的なインライン化など、変更可能なデータに対して実行できない多くの最適化が可能になります。可変データは、メモリブロックに多くの変更を加える必要がある場合に、パフォーマンスが向上する傾向があります。

不変性が本当に優れている場所に興味がある場合は、不変性のためにゼロから設計されたHaskellのような言語を見てください。

40
Phoshi

可変クラスと不変クラスを適切に定義するには、まずオブジェクトinstanceが可変であることの意味を定義する必要があります。オブジェクトのインスタンスは、カプセル化された状態が変更される可能性のある手段(*)がある場合、変更可能です。状態を変更できない場合、オブジェクトインスタンスは不変です。誰がクラスへの参照を保持しているかに関係なく、そのクラスのすべてのインスタンスが不変であることが保証されている場合、クラスは不変です。

(*)Reflectionのように、悪意のあるコードがほとんどすべてを変更できる特定のアプローチがあります。不変性に関するほとんどの議論は、コードが「合法的に」行うことに焦点を当てており、そのようなリフレクショントリックを違法と見なしています。セキュリティアプリケーション以外では、通常の目標は、正常に動作するクライアントに対してオブジェクトを正しく機能させることです。クライアントが不正なことをするときにクラスが奇妙に振る舞う場合、それはクライアントの問題です。

不変性に関する多くの議論は、不変クラスのすべてのインスタンスが不変である一方で、可変クラスのインスタンスはalsoが不変である可能性があるという事実を無視しています。可変性に関するいくつかの議論は、クラスが「実際に」不変であるためには、そのすべてのフィールドも不変型でなければならないことを示唆しています。これは真実ではないだけでなく、多くの場合、不変の配列型がないため、ほとんど不可能です。必要なことは、不変オブジェクトは常に、状態を保持する変更可能なオブジェクトへの参照が、それを変更する可能性のあるコードに公開されないことを確実にする必要があることです。外部のコードがカプセル化されたオブジェクトを正当に処理したい場合は、不変の包含オブジェクトによって、または不変のオブジェクトに代わって実装されたメソッドを介して実行する必要があります。

.NETまたはJavaに、特定のオブジェクトが、変更可能なタイプであっても、変更できないように指定する宣言的手段がある場合に役立ちます。残念ながら、そのような機能は存在しません。それにもかかわらず、主要な正確で効率的なコードを書くための鍵は、どの参照が不変クラスであると知られているものを識別するか、どの参照が可変クラスの不変インスタンスを識別するか(何である必要があるか)、可変クラスの非共有可変インスタンスを識別するか、これらのパターンのどれにも当てはまらない(一般に「エンティティ」を識別するため)。上記の種類の参照の1つを別の参照であるかのように使用すると、多くの種類のバグ(一部は非常に不明瞭な場合があります)が発生する可能性があります。

PS-フレームワークは、C++スタイルの「constの正確さ」の煩わしさなしに、const検証の効率と堅牢性を実現できます。

  • 「エンティティへの参照」、「潜在的に変更可能な値への無制限の参照」、「変更可能な値への読み取り専用参照」、および「不変値への参照」には、明確なストレージタイプが存在します

  • 値は複製可能です[含まれている参照のタイプが区別されている場合は、複製を大幅に自動化できます]

  • メンバーは、読み取り専用または不変の参照で呼び出し可能かどうかに基づいてマークできます

  • 各オブジェクトインスタンスには、それへの無制限の参照が存在するかどうかを示すフラグが含まれています

  • すべての参照を暗黙的に読み取り専用参照に変換できます。無制限の参照は、新しい不変のクローンを作成することにより、暗黙的に不変の参照に変換することもできます。読み取り専用の参照は暗黙的に不変に変換でき、必要に応じて新しい不変のクローンを作成できます。新しい可変オブジェクトは、任意の参照から複製できます。

「エンティティ」と「値」の違いの本質的な側面は、オブジェクトが「値」であることは、(1)不変である、or(2 )それは単一の所有者を持ち、その所有者の管理下にないオブジェクトへの参照ソートは存在しません。外部コードに自由に公開されたオブジェクトに存在する可能性のあるすべての参照を再び制御しようとすることは不可能であり、厳密にコンパイラーによって実行されるメカニズムはおそらく制限が多すぎて役に立ちませんが、C++のように機能したとしてもコードが「疑わしい」ように見えても、コードが適切なセマンティクスで動作することを約束できる「const-correctness」ですが、結果として発生した問題は、プログラマーが約束を破るのが原因であり、現状よりもはるかに有利です。

10
supercat

Javaではありませんが、 Eric Lippertの不変性に関する記事 をお勧めします。この問題は、Javaについて考えるほど明確なものではありません。正確な使用状況によってはパフォーマンスに影響を与える可能性がありますが、どちらを選択しても、パフォーマンスに固有の影響はありません。

重要な概念は、オブジェクトを変更できるかどうか、またどのように変更できるかです。

4
jmoreno

mutable-変更される可能性があります。

immutable-時間の経過とともに変化しない、または変更できない。

さて、定義があります。私たちはその言葉が何を意味するかを正確に知っていますが、それらはプログラミングにどのように適用されますか? 2つの新しい定義に移りましょう。

可変オブジェクト-変更される可能性のあるオブジェクト。

不変オブジェクト-時間の経過とともに変化しないか、変更できないオブジェクト。

オブジェクト指向プログラミングの観点から言えば、元の定義を直接使用できます。最後の質問は、これを実際にどのように使用するかです。

不変オブジェクトは構築時に初期化する必要があり、初期化後に他の不変オブジェクトにのみ依存する必要があります。

3
ChaosPandion

可変クラスと不変クラスの概念を理解する上で、より明確な画像を提供したいと思います。
Mutableは、その値を逆に変更できることを意味し、すでに述べたように、Immutableに当てはまります。
実際には、任意のクラスのコンストラクターを呼び出すときはいつでも、実際にそのタイプの変数のメモリ空間をヒープに割り当てています。例えば

String str1 = new String("john");
String str2 = str1 ;
System.out.println(str1.hashCode());
System.out.println(str2.hashCode());

ここで、文字列クラスのコンストラクターを呼び出したときに、値が "john"のこの変数にメモリが割り当てられ、その参照コードが変数str1に返されます。次のステートメントでは、str2も同じメモリロケーションをポイントしています。2つのprintlnステートメントは、両方の変数が同じメモリロケーションをポイントしていることを示す同じ値を出力します。今、このコードを書きます

str2 = "monk";
System.out.println(str2.hashCode());

この行を書き込むと、文字列型データ用の新しいメモリ空間がヒープに作成され、その値が「monk」に設定され、その参照がstr2に返されるため、str2は同じメモリの場所を指さなくなり、したがってprintlnステートメントで異なるハッシュコード値を取得します。その理由は、「文字列は不変クラスです」です。したがって、値を変更することはできません。 StringBufferクラスの場合、このコードを使用してその可変性を確認できます

StringBuffer sb = new StringBuffer("mohan");    
StringBuffer sb1 = sb.append(" kumar");
System.out.println(sb.hashCode());
System.out.println(sb1.hashCode()+" - "+(sb1==sb)+" - "+sb.hashCode());
3
Monalisa Das

プリミティブ(組み込み型)およびオブジェクト(ユーザー定義型)の観点から使用されます。不変とは、値を変更できないことを意味します。可変とは、値を変更できることを意味します。

多くの人は、プリミティブ変数とそれらの前にfinal修飾子を持つオブジェクト変数は不変であると考えていますが、これは正確には当てはまりません。 Finalは、変数が不変であることをほとんど意味しません。このサンプルコードのリンクをチェックしてください http://www.siteconsortium.com/h/D0000F.php

0
Jon