web-dev-qa-db-ja.com

不変オブジェクトが良い場合、なぜ人々は可変オブジェクトを作成し続けるのですか?

不変オブジェクト¹が優れており、シンプルで、並行プログラミングにメリットがある場合、なぜプログラマーは可変オブジェクトを作成し続けるのでしょうか²?

私はJavaプログラミングで4年の経験があり、それを見ると、クラスを作成した後に最初に行うことは、IDE (したがって、変更可能にします。)ほとんどのシナリオで、認識の欠如はありますか、または変更可能オブジェクトの使用を回避できますか?


¹ 不変オブジェクト は、作成後に状態を変更できないオブジェクトです。
² Mutable object は、作成後に変更できるオブジェクトです。

250

可変オブジェクトと不変オブジェクトのどちらにも、長所と短所があります。

不変オブジェクトは実際、多くの場合、人生をより単純にします。これらは、オブジェクトにIDがないため値を簡単に置き換えることができる値タイプに特に適しています。また、並行プログラミングをより安全かつクリーンにすることができます(並行性のバグを見つけるのが難しいことで有名なほとんどのバグは、最終的にはスレッド間で共有される変更可能な状態が原因です)。ただし、大きなオブジェクトや複雑なオブジェクトの場合、変更ごとにオブジェクトの新しいコピーを作成するのは非常にコストがかかり、退屈な作業になる可能性があります。また、識別が異なるオブジェクトの場合、既存のオブジェクトを変更する方が、変更された新しいコピーを作成するよりもはるかにシンプルで直感的です。

ゲームのキャラクターについて考えてみましょう。ゲームでは、速度が最優先事項であるため、変更可能なオブジェクトでゲームキャラクターを表現すると、ほとんどの場合、小さな変更ごとにゲームキャラクターの新しいコピーが生成される代替実装よりもゲームが大幅に高速になります。

さらに、現実世界に対する私たちの認識は、必然的に可変オブジェクトに基づいています。ガソリンスタンドで車を燃料で満たすと、それはずっと同じオブジェクトとして認識されます(つまり、identityは維持されますが、stateは変更されます)-ない空のタンクのある古い車が、次第に新しい車のインスタンスに置き換えられ、タンクが次第に満杯になるかのように。したがって、プログラムで実際のドメインをモデル化するときは常に、可変オブジェクトを使用してドメインモデルを実装し、実際のエンティティを表す方が簡単で簡単です。

これらすべての正当な理由とは別に、悲しいかな、人々が可変オブジェクトを作成し続ける最も可能性の高い原因は、心の慣性、つまり変化への抵抗です。今日のほとんどの開発者は、不変性(およびそれを含むパラダイム、関数型プログラミング)が影響力の領域で「トレンド」になる前に訓練を受けており、新しいツールや取引方法に関する知識を最新に保っていないことに注意してください。実際、私たち人間の多くは積極的にresist新しいアイデアとプロセスです。 「私はnn年間このようにプログラミングしており、最新の愚かな流行については気にしていません!」

326
Péter Török

あなたは皆、最も明白な答えを見逃していると思います。可変性は命令型言語のデフォルトであるため、ほとんどの開発者は可変オブジェクトを作成します。私たちのほとんどは、常にデフォルトから離れてコードを修正するよりも、より正確であるかどうかにかかわらず、私たちの時間との関係が優れています。そして、不変性は他のどのアプローチよりも万能薬ではありません。いくつかのことは簡単になりますが、いくつかの回答がすでに指摘しているように、他のものははるかに困難になります。

130
  1. 可変性のための場所があります。 ドメイン駆動設計 原則は、何を変更可能にし、何を不変にする必要があるかをしっかりと理解します。これについて考えると、オブジェクトへの状態のすべての変更が、オブジェクトとそれを参照するすべてのオブジェクトの破壊と再構成を必要とするシステムを想像することは非現実的であることがわかります。複雑なシステムでは、これにより、システム全体のオブジェクトグラフを完全に消去して再構築するのが簡単になります。

  2. ほとんどの開発者は、同時実行性(または情報に通じて一般に優れたプラクティスと見なされている他の多くの問題)に焦点を合わせる必要があるほどパフォーマンス要件が大きい場合は何もしません。

  3. 双方向の関係のように、不変オブジェクトでは簡単に実行できないことがいくつかあります。 1つのオブジェクトに関連付けの値を設定すると、IDが変更されます。したがって、他のオブジェクトに新しい値を設定すると、その値も変化します。問題は、参照を持つオブジェクトを表す新しいインスタンスが作成されたため、最初のオブジェクトの参照が無効になったことです。これを続けると、無限の回帰が発生するだけです。あなたの質問を読んだ後、私は少しケーススタディをしました、これはそれがどのように見えるかです。不変性を維持しながらそのような機能を可能にする代替アプローチはありますか?

        public class ImmutablePerson { 
    
         public ImmutablePerson(string name, ImmutableEventList eventsToAttend)
         {
              this.name = name;
              this.eventsToAttend = eventsToAttend;
         }
         private string name;
         private ImmutableEventList eventsToAttend;
    
         public string Name { get { return this.name; } }
    
         public ImmutablePerson RSVP(ImmutableEvent immutableEvent){
             // the person is RSVPing an event, thus mutating the state 
             // of the eventsToAttend.  so we need a new person with a reference
             // to the new Event
             ImmutableEvent newEvent = immutableEvent.OnRSVPReceived(this);
             ImmutableEventList newEvents = this.eventsToAttend.Add(newEvent));
             var newSelf = new ImmutablePerson(name, newEvents);
             return newSelf;
         }
        }
    
        public class ImmutableEvent { 
         public ImmutableEvent(DateTime when, ImmutablePersonList peopleAttending, ImmutablePersonList peopleNotAttending){
             this.when = when;     
             this.peopleAttending = peopleAttending;
             this.peopleNotAttending = peopleNotAttending;
         }
         private DateTime when; 
         private ImmutablePersonList peopleAttending;
         private ImmutablePersonList peopleNotAttending;
         public ImmutableEvent OnReschedule(DateTime when){
               return new ImmutableEvent(when,peopleAttending,peopleNotAttending);
         }
         //  notice that this will be an infinite loop, because everytime one counterpart
         //  of the bidirectional relationship is added, its containing object changes
         //  meaning it must re construct a different version of itself to 
         //  represent the mutated state, the other one must update its
         //  reference thereby obsoleting the reference of the first object to it, and 
         //  necessitating recursion
         public ImmutableEvent OnRSVPReceived(ImmutablePerson immutablePerson){
               if(this.peopleAttending.Contains(immutablePerson)) return this;
               ImmutablePersonList attending = this.peopleAttending.Add(immutablePerson);
               ImmutablePersonList notAttending = this.peopleNotAttending.Contains( immutablePerson ) 
                                    ? peopleNotAttending.Remove(immutablePerson)
                                    : peopleNotAttending;
               return new ImmutableEvent(when, attending, notAttending);
         }
        }
        public class ImmutablePersonList
        {
          private ImmutablePerson[] immutablePeople;
          public ImmutablePersonList(ImmutablePerson[] immutablePeople){
              this.immutablePeople = immutablePeople;
          }
          public ImmutablePersonList Add(ImmutablePerson newPerson){
              if(this.Contains(newPerson)) return this;
              ImmutablePerson[] newPeople = new ImmutablePerson[immutablePeople.Length];
              for(var i=0;i<immutablePeople.Length;i++)
                  newPeople[i] = this.immutablePeople[i];
              newPeople[immutablePeople.Length] = newPerson;
          }
          public ImmutablePersonList Remove(ImmutablePerson newPerson){
              if(immutablePeople.IndexOf(newPerson) != -1)
              ImmutablePerson[] newPeople = new ImmutablePerson[immutablePeople.Length-2];
              bool hasPassedRemoval = false;
              for(var i=0;i<immutablePeople.Length;i++)
              {
                 hasPassedRemoval = hasPassedRemoval || immutablePeople[i] == newPerson;
                 newPeople[i] = this.immutablePeople[hasPassedRemoval ? i + 1 : i];
              }
              return new ImmutablePersonList(newPeople);
          }
          public bool Contains(ImmutablePerson immutablePerson){ 
             return this.immutablePeople.IndexOf(immutablePerson) != -1;
          } 
        }
        public class ImmutableEventList
        {
          private ImmutableEvent[] immutableEvents;
          public ImmutableEventList(ImmutableEvent[] immutableEvents){
              this.immutableEvents = immutableEvents;
          }
          public ImmutableEventList Add(ImmutableEvent newEvent){
              if(this.Contains(newEvent)) return this;
              ImmutableEvent[] newEvents= new ImmutableEvent[immutableEvents.Length];
              for(var i=0;i<immutableEvents.Length;i++)
                  newEvents[i] = this.immutableEvents[i];
              newEvents[immutableEvents.Length] = newEvent;
          }
          public ImmutableEventList Remove(ImmutableEvent newEvent){
              if(immutableEvents.IndexOf(newEvent) != -1)
              ImmutableEvent[] newEvents = new ImmutableEvent[immutableEvents.Length-2];
              bool hasPassedRemoval = false;
              for(var i=0;i<immutablePeople.Length;i++)
              {
                 hasPassedRemoval = hasPassedRemoval || immutableEvents[i] == newEvent;
                 newEvents[i] = this.immutableEvents[hasPassedRemoval ? i + 1 : i];
              }
              return new ImmutableEventList(newPeople);
          }
          public bool Contains(ImmutableEvent immutableEvent){ 
             return this.immutableEvent.IndexOf(immutableEvent) != -1;
          } 
        }
    
49
smartcaveman

私は「純粋に機能的なデータ構造」を読んでいて、可変オブジェクトを使用して実装する方がはるかに簡単なデータ構造がかなりあることに気付きました。

二分探索木を実装するには、毎回新しい木を返す必要があります。新しい木は、変更された各ノードのコピーを作成する必要があります(変更されていないブランチは共有されます)。挿入機能の場合、これはそれほど悪くありませんが、私にとって、削除と再バランスの作業を始めたとき、事態はすぐにかなり非効率になりました。

もう1つ、オブジェクト指向のコードを何年も書くことができ、同時実行性の問題が発生するような方法でコードを実行しないと、共有された可変状態がどれほどひどいのかを理解できないこともあります。

36
Paul Sanwald

私の見解では、それは意識の欠如です。他の既知のJVM言語(Scala、Clojure)を見ると、変更可能なオブジェクトがコード内でめったに見られないため、シングルスレッドでは不十分なシナリオで人々がそれらを使用し始めます。

私は現在Clojureを学んでいて、Scala(4年+ =Javaも同様))で少し経験を持っています。あなたのコーディングスタイルは、状態。

29

Java Beansは特定のスタイルのオブジェクトの変化に大きく依存しており、(特にソースを考慮して)かなりの数の人がそれを(またはでもtheall Javaと記述する方法の正規の例。

13
Jerry Coffin

すべての企業Java私がキャリアで取り組んだシステムは、HibernateまたはJava Persistence API(JPA)のいずれかを使用しています。HibernateおよびJPAは基本的にデータオブジェクトに対する変更を検出して保存することが前提なので、システムは可変オブジェクトを使用します。多くのプロジェクトにとって、Hibernateがもたらす開発の容易さは、不変オブジェクトの利点よりも魅力的です。

明らかに、可変オブジェクトはHibernateよりもずっと前から存在しているため、Hibernateはおそらく可変オブジェクトの人気の最初の「原因」ではありません。多分可変オブジェクトの人気がHibernateの繁栄を許したのかもしれません。

しかし今日、多くのジュニアプログラマーがHibernateまたは別のORMを使用してエンタープライズシステムに歯を切れば、おそらく可変オブジェクトを使用する習慣を身につけるでしょう。 Hibernateのようなフレームワークは、可変オブジェクトの人気を定着させている可能性があります。

12
gutch

まだ言及されていない重要な点は、オブジェクトの状態を変更可能にすることで、その状態をカプセル化するオブジェクトのidentityを不変にすることが可能になるということです。

多くのプログラムは、本質的に変更可能な現実世界のものをモデル化するように設計されています。午前12時51分に、いくつかの変数AllTrucksがオブジェクト#451への参照を保持するとします。これは、その時点でフリートのすべてのトラックにどの貨物が含まれているかを示すデータ構造のルートです(12: 51am)、およびいくつかの変数BobsTruckを使用して、オブジェクト#24601への参照を取得すると、その時点(12:51 am)にボブのトラックに含まれている貨物を示すオブジェクトを指します。午前12時52分に、いくつかのトラック(ボブを含む)がロードおよびアンロードされ、データ構造が更新されて、AllTrucksが12の時点ですべてのトラックに貨物が入っていることを示すデータ構造への参照を保持します。 :52am。

BobsTruckはどうなりますか?

各トラックオブジェクトの 'cargo'プロパティが不変である場合、オブジェクト#24601は、ボブのトラックが午前12時51分に持っていた状態を永久に表します。 BobsTruckがオブジェクト#24601への直接参照を保持している場合、AllTrucksを更新するコードがBobsTruckも更新しない限り、ボブのトラックの現在の状態を表すことは停止します。 。さらに、BobsTruckが何らかの可変オブジェクトに格納されていない限り、AllTrucksを更新するコードがそれを更新できる唯一の方法は、コードが明示的にそうするようにプログラムされている場合に限られます。

すべてのオブジェクトを不変に保ちながら、BobsTruckを使用してボブのトラックの状態を監視できるようにしたい場合、BobsTruckを不変の関数にして、AllTrucksある特定の時間に持っていたか持っていた場合は、その時点でのボブのトラックの状態を示します。 1つは不変の機能のペアを保持することもできます-1つは上記のようになり、もう1つはフリート状態と新しいトラック状態への参照を受け入れ、新しいフリート状態への参照を返します。ボブのトラックが新しい状態になることを除いて、古いものと一致しました。

残念ながら、ボブのトラックの状態にアクセスするたびにそのような関数を使用しなければならないことは、かなり煩わしく煩わしく感じる可能性があります。別のアプローチは、オブジェクト#24601が常にそして永遠に(誰かがそれへの参照を保持している限り)ボブのトラックのcurrent状態を表すと言うことです。ボブのトラックの現在の状態に繰り返しアクセスするコードは、時間のかかる関数を毎回実行する必要はありません。オブジェクト#24601がボブのトラックであることを確認するために、ルックアップ関数を1回実行するだけです。ボブのトラックの現在の状態を見たいときにいつでもそのオブジェクトにアクセスします。

シングルスレッド環境、またはスレッドがデータを変更するのではなくデータを監視するだけのマルチスレッド環境では、機能的アプローチに利点がないわけではないことに注意してください。 AllTrucksに含まれるオブジェクト参照をコピーし、それによって表されるトラックの状態を調べるオブザーバースレッドは、参照を取得した時点でのすべてのトラックの状態を確認します。オブザーバースレッドが新しいデータを見たいときはいつでも、参照を再取得できます。一方、フリートの状態全体を単一の不変オブジェクトで表すと、2つのスレッドが異なるトラックを同時に更新する可能性が排除されます。これは、各スレッドが独自のデバイスに残されると、新しい「フリート状態」オブジェクトが生成されるためです。そのトラックの新しい状態と他のすべての古い状態。各スレッドが変更されていない場合にのみ各スレッドがCompareExchangeを使用してAllTrucksを更新し、状態オブジェクトを再生成して失敗したCompareExchangeに応答する場合、正確さが保証されます。操作ですが、複数のスレッドが同時書き込み操作を試みると、一般に、すべての書き込みが単一スレッドで行われた場合よりもパフォーマンスが低下します。このような同時操作を試行するスレッドが多いほど、パフォーマンスが低下します。

個々のトラックオブジェクトが変更可能であるが不変のIDがある場合、マルチスレッドシナリオはよりクリーンになります。特定のトラックで一度に操作できるスレッドは1つだけですが、異なるトラックで操作するスレッドは、干渉することなく操作できます。不変オブジェクトを使用する場合でもこのような動作をエミュレートできる方法があります(たとえば、「AllTrucks」オブジェクトを定義して、XXXに属するトラックの状態をSSSに設定するには、「[現在]現在」というオブジェクトを生成する必要があるだけです。 [XXX]に属するトラックの状態は[SSS]になり、他のすべての状態は[AllTrucksの古い値]になります。このようなオブジェクトの生成は、競合がある場合でもCompareExchangeループに時間がかかることはありません。一方、このようなデータ構造を使用すると、特定の人のトラックを見つけるのに必要な時間が大幅に増加します。immutable identitiesで可変オブジェクトを使用すると、それを回避できます問題。

9
supercat

正しいか間違っているかはありません、それはあなたが何を好むかに依存します。一部の人々が、あるパラダイムを別のパラダイムよりも優先し、あるデータモデルを別のパラダイムよりも優先する言語を好む理由があります。それはあなたの好みとあなたが達成したいことに依存します(そしてどちらか一方の熱狂的なファンを遠ざけることなく両方のアプローチを簡単に使用できることはいくつかの言語が求めている聖杯です)。

私はあなたの質問に答える最良かつ最も速い方法はあなたが頭に浮かぶことだと思います 不変性と変異性の長所と短所

8
haylem

このブログ投稿を確認してください: http://www.yegor256.com/2014/06/09/objects-should-be-immutable.html 。不変オブジェクトが可変オブジェクトより優れている理由を要約しています。引数の短いリストを次に示します。

  • 不変オブジェクトは、構築、テスト、使用が簡単です
  • 真に不変のオブジェクトは常にスレッドセーフです
  • それらは一時的な結合を回避するのに役立ちます
  • それらの使用法には副作用がありません(防御的なコピーはありません)
  • アイデンティティの可変性の問題が回避されます
  • 彼らは常に失敗の原子性を持っています
  • キャッシュがはるかに簡単です

私が理解している限り、人々は可変オブジェクトを使用しています。なぜなら、彼らはまだ命令型手続きプログラミングとOOP=を混合しているからです。

7
yegor256

In Java不変オブジェクトには、オブジェクトのすべてのプロパティを取得するコンストラクタが必要です(またはコンストラクタがその他の引数またはデフォルト)。これらのプロパティすべきマークfinal

これには主にデータバインディングに関係する4つの問題があります。

  1. Javaコンストラクターのリフレクションメタデータは、引数名を保持しません。
  2. Javaコンストラクター(およびメソッド)には名前付きパラメーター(ラベルとも呼ばれる)がないため、多くのパラメーターと混同されます。
  3. 別の不変オブジェクトを継承する場合、コンストラクターの適切な順序を呼び出す必要があります。これは、フィールドの1つをあきらめて、ファイナルではないままにするという点で、かなりトリッキーです。
  4. ほとんどのバインディング技術(Spring MVCデータバインディング、Hibernateなど)は、引数のないデフォルトコンストラクターでのみ機能します(これは、注釈が常に存在するとは限らなかったためです)。

@ConstructorProperties のようなアノテーションを使用して#1と#2を軽減し、別の可変ビルダーオブジェクト(通常は流暢)を作成して、不変オブジェクトを作成できます。

5
Adam Gent

誰もパフォーマンス最適化のメリットについて言及していないことに驚いています。言語によっては、データが変更されないことがわかっているため、コンパイラーは不変データを処理するときに一連の最適化を行うことができます。あらゆる種類のものがスキップされ、パフォーマンスが大幅に向上します。

また、不変オブジェクトは、クラス全体の状態バグをほぼ排除します。

難しいので、すべての言語に適用されるわけではなく、ほとんどの人が命令型コーディングを教えられたので、本来の大きさほど大きくありません。

また、ほとんどのプログラマーは自分の箱で満足しており、完全に理解していない新しいアイデアに抵抗することが多いことがわかりました。一般的に人々は変化を好まない。

また、ほとんどのプログラマーの状態は悪いことを覚えておいてください。実際に行われているプログラミングのほとんどはひどいものであり、理解と政治の欠如が原因です。

5
Jay

なぜ人々は強力な機能を使用するのですか?なぜ人々はメタプログラミング、遅延、または動的型付けを使用するのですか?答えは便利です。可変状態はとても簡単です。インプレースでの更新は非常に簡単であり、変更可能な状態よりも変更不可能な状態で作業する方が生産性が高いプロジェクトサイズのしきい値は非常に高いので、しばらくの間、選択肢に悩まされることはありません。

4
dan_waterworth

プログラミング言語は、コンピュータで実行できるように設計されています。コンピュータのすべての重要なビルディングブロック(CPU、RAM、キャッシュ、ディスク)は変更可能です。それらが(BIOS)でない場合、それらは実際には不変であり、新しい不変オブジェクトを作成することもできません。

したがって、不変オブジェクトの上に構築されたプログラミング言語は、その実装における表現のギャップに悩まされます。 Cなどの初期の言語では、それは大きな障害でした。

4
MSalters

可変オブジェクトは、オブジェクトをインスタンス化した後で複数の値を設定する必要がある場合に使用されます。

たとえば、6つのパラメーターを持つコンストラクターは必要ありません。代わりに、setterメソッドを使用してオブジェクトを変更します。

この例は、フォント、向きなどのセッターを備えたReportオブジェクトです。

簡単に言うと、オブジェクトに設定する状態がたくさんあり、コンストラクターシグネチャが非常に長いのは実用的でない場合に、ミュータブルは便利です。

編集:ビルダーパターンを使用して、オブジェクトの状態全体を構築できます。

1

多くの人々が良い答えをしていたので、私があなたが触れた非常に注意深く、非常に真実であり、ここでは他に言及されていない何かを指摘したいと思います。

セッターとゲッターを自動的に作成することは恐ろしい、恐ろしいアイデアですが、手続き志向の人々がOOを自分のマインドセットに押し込もうとする最初の方法です。プロパティとともにセッターとゲッターは、デフォルトではなく、それらが必要であることがわかります

実際、かなり定期的にゲッターが必要ですが、セッターまたは書き込み可能なプロパティがコードに存在する唯一の方法は、オブジェクトが完全にインスタンス化された後にロックされるビルダーパターンを使用することです。

多くのクラスは作成後に変更可能です。これは問題ありません。プロパティを直接操作する必要はありません。実際のビジネスロジックを含むメソッド呼び出しを通じてプロパティを操作するように求められる必要があります(はい、セッターはほとんど同じです)プロパティを直接操作するようなもの)

これは実際には「スクリプト」スタイルのコード/言語には適用されませんが、他の人のために作成したコードに適用され、他の人が何年にもわたって繰り返し読むことを期待しています。私は最近、Groovyをいじることをとても楽しんでいて、ターゲットに大きな違いがあるので、その区別を始めなければなりませんでした。

1
Bill K

可変オブジェクトの使用は、命令型思考に由来すると思います。可変変数の内容を段階的に変更して結果を計算します(副作用による計算)。

機能的に考える場合、不変の状態を持ち、関数を適用して古い値から新しい値を作成することにより、システムの後続の状態を表現する必要があります。

機能的なアプローチはよりクリーンで堅牢なものになりますが、コピーのために非常に非効率になる可能性があるため、段階的に変更する共有データ構造にフォールバックする必要があります。

私が最も妥当だと思うトレードオフは次のとおりです。不変オブジェクトから始めて、実装が十分に高速でない場合は可変オブジェクトに切り替えます。この観点から、可変オブジェクトを最初から体系的に使用することは、ある種の時期尚早な最適化と見なすことができます。最初から実装を理解し、デバッグします。

では、なぜ多くのプログラマーが可変オブジェクトを使用するのでしょうか?私見には2つの理由があります。

  1. 多くのプログラマーは、命令型(手続き型またはオブジェクト指向)パラダイムを使用してプログラミングする方法を学びました。したがって、可変性は、計算を定義するための基本的なアプローチです。
  2. 多くのプログラマーはパフォーマンスを早めに心配しますが、多くの場合、最初は機能的に正しいプログラムを書くことに集中し、次にボトルネックを見つけて最適化しようとする方が効果的です。
1
Giorgio

私はあなたがJavaについて質問しているのを知っていますが、私はいつでも、objective-cでミュータブルvsイミュータブルを使用しています。不変配列NSArrayと可変配列NSMutableArrayがあります。これらは、正確な使用を処理するために最適化された方法で具体的に記述された2つの異なるクラスです。配列を作成する必要があり、その内容を変更しない場合は、可変配列と比較して、オブジェクトが小さく、処理速度が速いNSArrayを使用します。

したがって、不変のPersonオブジェクトを作成する場合は、コンストラクターとゲッターのみが必要です。したがって、オブジェクトが小さくなり、使用するメモリが少なくなるため、プログラムが実際に高速になります。作成後にオブジェクトを変更する必要がある場合は、変更可能なPersonオブジェクトを使用すると、新しいオブジェクトを作成する代わりに値を変更できます。

So:オブジェクトをどのように処理するかによって異なりますが、ミュータブルvsイミュータブルを選択すると、パフォーマンスに関して大きな違いが生まれます。

1

変更可能なオブジェクトがなければ、状態はありません。確かに、これを管理でき、オブジェクトが複数のスレッドから参照される可能性がある場合、これは良いことです。しかし、プログラムはかなり退屈になるでしょう。多くのソフトウェア、特にWebサーバーは、データベース、オペレーティングシステム、システムライブラリなどの可変性を無効にすることで、可変オブジェクトの責任を回避します。実際問題として、これによりプログラマーは可変性の問題から解放され、Web(およびその他)が作成されます。手頃な価格の開発。しかし、可変性はまだそこにあります。

一般に、クラスには3つのタイプがあります。通常のスレッドセーフでないクラスであり、慎重に保護および保護する必要があります。自由に使用できる不変クラス。自由に使用できますが、細心の注意を払って記述する必要がある、変更可能なスレッドセーフなクラスです。最初のタイプは厄介なタイプで、最悪のものは3番目のタイプであると考えられるものです。もちろん、最初のタイプは簡単に書くことができます。

私は通常、非常に注意深く見守らなければならない、通常の変更可能なクラスをたくさん作成します。マルチスレッドの状況では、致命的な抱擁を回避できる場合でも、必要な同期によりすべてが遅くなります。したがって、私は通常、変更可能なクラスの不変のコピーを作成し、それを使用できる人に渡します。元の状態が変化するたびに新しい不変のコピーが必要になるので、時々、オリジナルのコピーが100個あると想像します。私はガベージコレクションに完全に依存しています。

要約すると、複数のスレッドを使用していない場合は、スレッドセーフではなく、変更可能なオブジェクトで問題ありません。 (しかし、マルチスレッディングはあらゆる場所で活用されています-注意してください!)それらをローカル変数に制限したり、厳密に同期させたりすれば、安全に使用できます。他の人が証明したコード(DB、システムコールなど)を使用して回避できる場合は、そうします。不変クラスを使用することができる場合は、そうします。そして、私は考える一般では、人々はマルチスレッドの問題に気づいていないまたはは(賢明に)それらを恐れており、マルチスレッドを回避するためにあらゆる種類のトリックを使用しています(または、責任を他の場所に押し込んでいます)。

追伸:私はJavaゲッターとセッターが手に負えなくなっていると感じています。チェック this アウト。

1
RalphChapin

ここで示されている他の多くの理由に加えて、問題は主流の言語が不変性をうまくサポートしていないことです。少なくとも、constやfinalのような追加のキーワードを追加しなければならず、多くの引数またはコードが長いビルダーパターンを持つ判読不能なコンストラクターを使用する必要があるという点で、不変性のペナルティがあります。

不変性を念頭に置いて作られた言語では、これははるかに簡単です。次のScalaスニペットを検討してください。オプションで名前付き引数を使用してクラスPersonを定義し、変更された属性でコピーを作成します。

case class Person(id: String, firstName: String, lastName: String)

val joe = Person("123", "Joe", "Doe")
val spouse = Person(id = "124", firstName = "Mary", lastName = "Moe")
val joeMarried = joe.copy(lastName = "Doe-Moe")

したがって、開発者に不変性を採用してほしい場合、これが、より近代的なプログラミング言語への切り替えを検討する理由の1つです。

1

イミュータブルとは値を変更できないことを意味し、ミュータブルとはプリミティブやオブジェクトの観点から考えると値を変更できることを意味します。 Javaの点で、オブジェクトはプリミティブとは異なります。オブジェクトは型に組み込まれています。プリミティブは、int、boolean、voidなどの型に組み込まれています。

多くの人々は、前にfinal修飾子があるプリミティブとオブジェクト変数は不変であると考えていますが、これは正確には当てはまりません。したがって、最後は変数に対して不変を意味することはほとんどありません。コードサンプルは this link をチェックしてください:

public abstract class FinalBase {

    private final int variable; // Unset

    /* if final really means immutable than
     * I shouldn't be able to set the variable
     * but I can.
     */
    public FinalBase(int variable) { 
        this.variable = variable;
    }

    public int getVariable() {
        return variable;
    }

    public abstract void method();
}

// This is not fully necessary for this example
// but helps you see how to set the final value 
// in a sub class.
public class FinalSubclass extends FinalBase {

    public FinalSubclass(int variable) {
        super(variable);
    }

    @Override
    public void method() {
        System.out.println( getVariable() );
    }

    @Override
    public int getVariable() {

        return super.getVariable();
    }

    public static void main(String[] args) {
        FinalSubclass subclass = new FinalSubclass(10);
        subclass.method();
    }
}
0
Jon