web-dev-qa-db-ja.com

なぜJava Map extend Collection?

_Map<?,?>_は_Collection<?>_ではないという事実に驚きました。

私はそれがそのように宣言されている場合、それは理にかなっているだろうと思った:

_public interface Map<K,V> extends Collection<Map.Entry<K,V>>
_

結局、_Map<K,V>_は _Map.Entry<K,V>_ のコレクションですよね?

それで、それがそのように実装されない理由はありますか?


最も信頼できる答えをくれたCletusに感謝しますが、なぜ_Map<K,V>_を_Set<Map.Entries<K,V>>_として(entrySet()を介して)既に表示できるのか、まだ疑問に思っています。代わりにそのインターフェイスを拡張します。

MapCollectionである場合、要素は何ですか?唯一の合理的な答えは「キーと値のペア」です

まさに、_interface Map<K,V> extends Set<Map.Entry<K,V>>_は素晴らしいでしょう!

しかし、これは非常に限られた(特に有用ではない)Map抽象化を提供します。

しかし、その場合、インターフェイスでentrySetが指定されているのはなぜですか?それは何らかの形で役立つに違いありません(そして、その立場について議論するのは簡単だと思います!)。

特定のキーがどの値にマップされているかを尋ねたり、どの値にマップされているかを知らずに特定のキーのエントリを削除したりすることはできません。

Mapについてはそれだけだと言っているのではありません! should他のすべてのメソッドを保持できます(entrySetは例外です)。

141

Java Collections API Design FAQ から:

Mapがコレクションを拡張しないのはなぜですか?

これは仕様によるものです。マッピングはコレクションではなく、コレクションはマッピングではないと感じています。したがって、MapがCollectionインターフェイスを拡張する(またはその逆)ことはほとんど意味がありません。

マップがコレクションの場合、要素は何ですか?唯一の妥当な答えは「キーと値のペア」ですが、これは非常に限られた(特に有用ではない)マップ抽象化を提供します。特定のキーがどの値にマップされているかを尋ねたり、どの値にマップされているかを知らずに特定のキーのエントリを削除したりすることはできません。

Mapを拡張するためにコレクションを作成することもできますが、これにより疑問が生じます。キーとは何ですか本当に満足のいく答えはありません。また、強制すると不自然なインターフェースになります。

マップは(キー、値、またはペアの)コレクションとして表示でき、この事実は、マップの3つの「コレクションビュー操作」(keySet、entrySet、およびvalues)に反映されます。原則として、要素へのインデックスをマッピングするマップとしてリストを表示することは可能ですが、リストから要素を削除すると、削除された要素の前にすべての要素に関連付けられたキーが変更されるという厄介なプロパティがあります。そのため、リストにマップビュー操作がありません。

更新:引用はほとんどの質問に答えると思います。特に有用な抽象化ではないエントリのコレクションに関する部分を強調する価値があります。例えば:

_Set<Map.Entry<String,String>>
_

許可します:

_set.add(entry("hello", "world"));
set.add(entry("hello", "world 2");
_

(_Map.Entry_インスタンスを作成するentry()メソッドを想定)

Mapsには一意のキーが必要なので、これに違反します。または、エントリのSetに一意のキーを課す場合、それは実際にはSetではありません。それはSetであり、さらに制限があります。

おそらく、あなたは_Map.Entry_のequals()/hashCode()関係が純粋にキーにあったと言えますが、それにも問題があります。さらに重要なことは、それは本当に何か価値を追加するのでしょうか?この抽象化は、コーナーケースを調べ始めると壊れることがあります。

HashSetが実際にHashMapとして実装されていることは注目に値しますが、逆ではありません。これは純粋に実装の詳細ですが、それでも興味深いものです。

entrySet()が存在する主な理由は、トラバーサルを単純化して、キーをトラバースしてからキーのルックアップを行う必要がないようにすることです。 MapがエントリのSet(imho)であるべきであるという一応の証拠としてそれを受け取らないでください。

121
cletus

whyは主観的だと思います。

C#では、Dictionaryはコレクションを拡張するか、少なくともコレクションを実装すると思います。

public class Dictionary<TKey, TValue> : IDictionary<TKey, TValue>, 
    ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, 
    IDictionary, ICollection, IEnumerable, ISerializable, IDeserializationCallback

ファロスモールタックでも:

Collection subclass: #Set
Set subclass: #Dictionary

しかし、いくつかの方法には非対称性があります。例えば、 collect:は関連付け(エントリと同等)を取り、do:値を取ります。別のメソッドkeysAndValuesDo:エントリごとに辞書を反復処理します。 Add:は関連付けを取りますが、remove:は「抑制」されています。

remove: anObject
self shouldNotImplement 

したがって、それは間違いなく実行可能ですが、クラス階層に関する他のいくつかの問題につながります。

より良いのは主観的です。

10
ewernli

あなたの質問をかなり直接的にカバーする多くの答えを手に入れましたが、少し戻って、より一般的に質問を見てみると役立つと思います。つまり、Javaライブラリがどのように書かれているのかを具体的に見てはならず、そのように書かれている理由を見てはいけません。

ここでの問題は、継承がoneタイプの共通性のみをモデル化することです。どちらも「コレクションのような」ものと思われる2つのものを選択した場合、おそらく共通している8つまたは10の物を選択できます。 「コレクションのような」ものの別のペアを選択した場合、それらは共通して8または10のものになりますが、最初のペアとして同じ 8または10のものにはなりません。

十数種類の「コレクションのような」ものを見ると、それらのほぼすべてが、少なくとも他の1つと共通の8または10の特性を持っているでしょう。しかし、共有されているものを見ると、 everyそれらのうちの1つ、実質的に何も残されていません。

これは、継承(特に単一の継承)がうまくモデル化されない状況です。それらのどれが実際のコレクションであるか、そうでないかの間に明確な境界線はありませんが、意味のあるCollectionクラスを定義したい場合、それらのいくつかを除外することに固執しています。それらのいくつかのみを残した場合、Collectionクラスは非常にまばらなインターフェイスのみを提供できます。省略した場合は、よりリッチなインターフェイスを提供できます。

また、基本的に「このタイプのコレクションは操作Xをサポートしますが、Xを定義する基本クラスから派生することにより、使用を許可されていませんが、派生クラスのXを使用しようとすると失敗します」 、例外をスローすることにより)。

それでも、1つの問題が残ります。ほとんどの場合、どのクラスを除外し、どのクラスを配置するかに関係なく、どのクラスが入っているか、何が出ているかを明確に区別する必要があります。その線をどこに引いても、quiteに似ているいくつかのものの間の明確で、むしろ人工的な分割が残されます。

10
Jerry Coffin

クレタスの答えは良いですが、意味論的なアプローチを追加したいと思います。両方を組み合わせても意味がありません。コレクションインターフェイスを介してキーと値のペアを追加し、キーが既に存在する場合を考えてください。 Map-interfaceは、キーに関連付けられた1つの値のみを許可します。ただし、同じキーを使用して既存のエントリを自動的に削除すると、コレクションの追加後は以前と同じサイズになります-コレクションにとっては非常に予期していません。

3
Mnementh

Javaコレクションが壊れています。不足しているインターフェイス、リレーションのインターフェイスがあります。したがって、MapはRelationを拡張し、Setは拡張します。リレーション(マルチマップとも呼ばれる)には、一意の名前と値のペアがあります。マップ(別名「関数」)には、もちろん値にマップする一意の名前(またはキー)があります。シーケンスはマップを拡張します(各キーは0より大きい整数です)。バッグ(またはマルチセット)はマップを拡張します(各キーは要素であり、各値は要素がバッグに表示される回数です)。

この構造により、「コレクション」の範囲の交差、結合などが可能になります。したがって、階層は次のようになります。

                                Set

                                 |

                              Relation

                                 |

                                Map

                                / \

                             Bag Sequence

Sun/Oracle/Java ppl-次回は正しく入手してください。ありがとう。

2
xagyg

それぞれのデータ構造を見ると、MapCollectionの一部ではない理由を簡単に推測できます。各Collectionには単一の値が格納され、Mapにはキーと値のペアが格納されます。したがって、Collectionインターフェイスのメソッドは、Mapインターフェイスと互換性がありません。たとえば、Collectionにはadd(Object o)があります。 Mapでのそのような実装はどうなりますか。 Mapにそのようなメソッドがあると意味がありません。代わりに、Mapput(key,value)メソッドがあります。

addAll()remove()、およびremoveAll()メソッドにも同じ引数が使用されます。したがって、主な理由は、データがMapCollectionに格納される方法の違いです。また、Collectionインターフェースが実装されているIterableインターフェースを思い出す場合、つまり.iterator()メソッドを持つインターフェースはCollection。では、そのようなメソッドはMapに対して何を返しますか?キーイテレータまたは値イテレータこれも意味がありません。

Mapのキーと値のストアを反復処理する方法があり、それがCollectionフレームワークの一部です。

1
Mayur Ingle

Map<K,V>Set<Map.Entry<K,V>>を拡張しないでください。

  • できない同じMap.Entrysを同じMapに追加しますが、
  • can同じMap.Entryに同じキーを持つ異なるSet<Map.Entry>sを追加します。
0
einpoklum

まさに、_interface Map<K,V> extends Set<Map.Entry<K,V>>_は素晴らしいでしょう!

実際、それが_implements Map<K,V>, Set<Map.Entry<K,V>>_である場合、私は同意する傾向があります。しかし、それはあまりうまくいきませんよね? _HashMap implements Map<K,V>, Set<Map.Entry<K,V>_、_LinkedHashMap implements Map<K,V>, Set<Map.Entry<K,V>_などがありますが、それはすべて良いですが、entrySet()があれば、そのメソッドの実装を忘れることはありません。実装者が両方のインターフェースを実装していることを望んでいるのではないのに対して、あなたはどんなMapのentrySetも取得することができます...

_interface Map<K,V> extends Set<Map.Entry<K,V>>_を持ちたくないのは、単にメソッドが増えるからです。そして、結局のところ、それらは異なるものですよね?また、非常に実用的には、IDEで_map._をヒットした場合、.remove(Object obj).remove(Map.Entry<K,V> entry)を表示したくないのは、_hit ctrl+space, r, return_を実行できないためです。それで完了です。

0
Enno Shioji