web-dev-qa-db-ja.com

Java 8に不変のコレクションが含まれていないのはなぜですか?

Javaチームは、Java 8.の関数型プログラミングへの障壁を取り除くために多くの素晴らしい仕事をしました。特に、Java.utilコレクションへの変更により、変換を非常に高速なストリーム操作に連鎖する素晴らしい仕事です。コレクションにファーストクラスの関数と関数メソッドを追加したことを考えると、不変のコレクションまたは不変のコレクションインターフェイスさえも完全に提供できなかったのはなぜですか。

Javaチームは、既存のコードを変更せずに、いつでも変更可能なものと同じ不変のインターフェースを追加し、 "set"メソッドを除いて、既存のインターフェースをそれらから拡張することができます。この:

                  ImmutableIterable
     ____________/       |
    /                    |
Iterable        ImmutableCollection
   |    _______/    /          \   \___________
   |   /           /            \              \
 Collection  ImmutableList  ImmutableSet  ImmutableMap  ...
    \  \  \_________|______________|__________   |
     \  \___________|____________  |          \  |
      \___________  |            \ |           \ |
                  List            Set           Map ...

確かに、List.add()やMap.put()などの操作は現在、指定されたキーに対してブール値または以前の値を返し、操作が成功したか失敗したかを示します。不変のコレクションは、そのようなメソッドをファクトリーとして扱い、追加された要素を含む新しいコレクションを返す必要があります。これは、現在の署名と互換性がありません。しかし、ImmutableList.append()または.addAt()およびImmutableMap.putEntry()のような別のメソッド名を使用することで回避できます。結果の冗長性は、不変のコレクションを操作する利点よりも重要であり、型システムは、間違ったメソッドを呼び出すエラーを防止します。時間の経過とともに、古いメソッドは廃止される可能性があります。

不変コレクションの勝利:

  • 単純さ-基礎となるデータが変更されない場合、コードについての推論がより簡単になります。
  • ドキュメンテーション-メソッドが不変のコレクションインターフェースを取る場合、そのコレクションは変更されないことがわかります。メソッドが不変のコレクションを返す場合は、それを変更できないことがわかります。
  • 並行性-不変のコレクションをスレッド間で安全に共有できます。

不変性を前提とする言語を味わったことのある人として、猛威を振るう突然変異のワイルドウエストに戻ることは非常に困難です。 Clojureのコレクション(シーケンスの抽象化)には、Java 8コレクションが提供するすべての要素に加えて、不変性(ただし、ストリームではなく同期リンクリストが原因で余分なメモリと時間を使用する可能性があります)がすべて含まれています)Scalaには、操作の完全なセットを備えた変更可能なコレクションと不変のコレクションの両方があり、これらの操作は熱心ですが、.iteratorを呼び出すと遅延ビューが提供されます(遅延でそれらを評価する他の方法があります)。 Javaは、不変のコレクションなしで競争を続けることができます。

誰かがこれについての歴史や議論を私に指摘できますか?確かにどこかで公開されています。

136
GlenPeterson

不変のコレクションを使用するには、共有が絶対に必要だからです。それ以外の場合は、すべての操作で他のリスト全体がどこかのヒープにドロップされます。 Haskellのように完全に不変の言語は、積極的な最適化や共有を行わずに驚くほどの量のゴミを生成します。 50要素未満でのみ使用できるコレクションを用意しても、標準ライブラリに入れる価値はありません。

さらに、不変コレクションは、多くの場合、変更可能なコレクションとは根本的に異なる実装を持っています。たとえばArrayListを考えてみましょう。効率的な不変関数ArrayListは配列にはなりません! Clojureは32のIIRCを使用して、分岐係数の大きいバランスの取れたツリーで実装する必要があります。機能的な更新を追加するだけで可変コレクションを「不変」にすることは、メモリリークと同様にパフォーマンスのバグです。

さらに、Javaでは共有は実行できません。 Javaは、可変性と参照の等価性に無制限のフックを提供しすぎるため、「最適化のみ」を共有することができません。リスト内の要素を変更して、そのリストの他の20バージョンの要素を変更しただけです。

これはまた、効率的な不変性、共有、ストリームの融合のための非常に重要な最適化の巨大なクラスを除外します。 (FP伝道者)にとって良いスローガンになるでしょう)

115
Daniel Gratzer

可変コレクションは、不変コレクションのサブタイプではありません。代わりに、変更可能なコレクションと不変のコレクションは、読み取り可能なコレクションの兄弟の子孫です。残念ながら、「読み取り可能」、「読み取り専用」、および「不変」の概念は、3つの異なる意味があるとしても、あいまいになっているようです。

  • 読み取り可能なコレクションの基本クラスまたはインターフェイスタイプは、アイテムを読み取ることができることを約束し、コレクションを変更する直接的な手段を提供しませんが、参照を受け取るコードが変更を許可するような方法でキャストまたは操作できないことを保証しません。

  • 読み取り専用のコレクションインターフェイスには新しいメンバーは含まれていませんが、コレクションを変更したり何かへの参照を受け取ったりするような方法で参照を操作する方法がないことを約束するクラスによってのみ実装する必要がありますそうすることができます。ただし、コレクションが内部への参照を持つ他の何かによって変更されることはありません。読み取り専用のコレクションインターフェースでは、変更可能なクラスによる実装を防止できない場合がありますが、任意の実装、または実装から派生したクラスを指定できます。これにより、変更は「不正な」実装または実装の派生物と見なされます。 。

  • 不変コレクションとは、コレクションへの参照が存在する限り、常に同じデータを保持するコレクションです。特定の要求に応答して常に同じデータを返すわけではない不変のインターフェースの実装はすべて壊れています。

同じ「読み取り可能」タイプを実装または派生する強い関連性を持つ可変および不変のコレクションタイプを用意し、読み取り可能タイプにAsImmutableAsMutable、およびAsNewMutableメソッドを含めると便利な場合があります。このような設計により、コレクション内のデータを永続化したいコードがAsImmutableを呼び出すことができます。このメソッドは、コレクションが変更可能な場合は防御コピーを作成しますが、すでに変更不可能な場合はコピーをスキップします。

79
supercat

Java Collections Frameworkは、 Java.util.Collectionsクラスの6つの静的メソッドを使用して、コレクションの読み取り専用バージョンを作成する機能を提供します

元の質問へのコメントで誰かが指摘したように、コレクションを変更できない(メンバーをそのようなコレクションに追加または削除できない)場合でも、返されるコレクションは不変とは見なされない場合があります。オブジェクトタイプで許可されている場合は変更できます。

ただし、コードが単一のオブジェクトを返すか、変更不可能なオブジェクトのコレクションを返すかに関係なく、この問題は残ります。タイプがそのオブジェクトの変更を許可する場合、その決定はタイプの設計で行われたものであり、JCFへの変更がそれをどのように変更できるかはわかりません。不変性が重要な場合、コレクションのメンバーは不変タイプである必要があります。

15
Arkanon

これは非常に良い質問です。 Javaで記述されたすべてのコードの中で、世界中の何百万ものコンピューターで毎日24時間稼働しているので、総クロックサイクルの約半分が無駄にならなければならないという考えを楽しんでいます。関数によって返されているコレクションの安全なコピーを作成するだけです(これらのコレクションの作成後、ミリ秒後にガベージコレクションします)。

Java=プログラマの割合は、CollectionsクラスのメソッドのunmodifiableCollection()ファミリの存在を認識していますが、それらの間でも、多くは単に気にしないで。

そして私はそれらを責めることはできません:読み取り/書き込みのふりをしているが、「書き込み」メソッドのいずれかの呼び出しを間違えた場合にUnsupportedOperationExceptionをスローするインターフェイスは、非常に悪いことです!

これで、Collectionのような、add()remove()、およびclear()メソッドがないインターフェイスは、「ImmutableCollection」インターフェイスにはなりません。 「UnmodifiableCollection」インターフェースになります。実際、不変性は実装の性質であり、インターフェースの特性ではないため、「ImmutableCollection」インターフェースが存在することはあり得ません。よくわかりません。説明させてください。

誰かがそのような読み取り専用のコレクションインターフェイスを手渡したとします。別のスレッドに渡しても安全ですか?それが本当に不変のコレクションを表すことを確実に知っているなら、答えは「はい」でしょう。残念ながら、これはインターフェースであるため、実装方法がわかりません。そのため、答えはnoである必要があります:ご存知のように、実際には変更可能なコレクションの(あなたにとって)変更不可能なビューである可能性があります(Collections.unmodifiableCollection()、で取得したものと同様)。変更すると、破損したデータが読み取られます。

したがって、本質的に説明したのは、「変更不可」ではなく「変更不可」のコレクションインターフェイスのセットです。 「変更不可」とは、そのようなインターフェイスへの参照を持つユーザーが基になるコレクションを変更できないことを意味することを理解することが重要です。また、基になるコレクションが必ずしも不変であるためではなく、インターフェイスに変更メソッドがないために、それらを防止できます。基礎となるコレクションは、おそらく変更可能です。あなたはそれについて何の知識も制御もしません。

不変のコレクションを取得するには、それらがインターフェイスではなくclassesである必要があります!

これらの不変コレクションクラスは最終的なものである必要があるため、そのようなコレクションへの参照が与えられると、自分や、それを参照している他の誰かが不変コレクションとして動作することが確実にわかるでしょう。それでやりなさい。

したがって、Javaでコレクションのcompleteセット(またはその他の宣言型命令言語)を使用するには、次のものが必要です。

  1. 変更不可コレクションインターフェースのセット。

  2. mutableコレクションinterfacesのセットで、変更不可能なものを拡張します。

  3. 一連のmutableコレクションclassesは、変更可能なインターフェースを実装し、拡張により、変更不可能なインターフェースも実装します。

  4. immutableコレクションclassesのセットであり、変更不可能なインターフェースを実装しますが、ほとんどの場合、不変性を保証するためにクラスとして渡されます。

私は上記のすべてを面白くするために実装し、プロジェクトでそれらを使用していて、それらは魅力のように機能します。

それらがJavaランタイムの一部ではない理由は、おそらくこれが多すぎる/複雑すぎる/理解するのが難しすぎると考えられたためです。

個人的には、私が上で説明したことは十分ではないと思います。必要と思われるもう1つのことは、構造不変性の一連の変更可能なインターフェースとクラスです。 (接頭辞「StructurallyImmutable」が長すぎるため、これは単に「Rigid」と呼ばれる場合があります。)

8
Mike Nakis

不変のコレクションは、相互に比較して深く再帰的になる可能性があり、オブジェクトの等価性がsecureHashによる場合、不当に非効率的ではありません。これはマークルフォレストと呼ばれます。これは、コレクションごと、またはソートされたマップの(セルフバランシングバイナリ)AVLツリーのようなそれらの一部内にあります。

これらのコレクション内のすべてのJava=オブジェクトが一意のIDまたはハッシュするビット文字列を持たない限り、コレクションには一意に名前を付けるハッシュするものがありません。

例:4x1.6ghzのラップトップでは、1つのハッシュサイクル(最大55バイト)に収まる最小サイズの毎秒200Kのsha256を実行できますが、longのハッシュテーブルでは500KのHashMap演算または3Mの演算を実行できます。 1秒あたり200K/log(collectionSize)の新しいコレクションは、データの整合性と匿名のグローバルスケーラビリティが重要な場合に十分高速です。

2
Ben Rayfield