Java MapがCollectionを拡張しないのはなぜですか?


146

ないことに驚い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())、それだけではなく、そのインタフェースを拡張しません。

a Mapがの場合Collection、要素は何ですか?唯一の合理的な答えは「キーと値のペア」です

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

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

しかし、その場合は、なぜentrySetインターフェースによって指定されるのでしょうか。それはどういうわけか有用であるに違いありません(そして私はその立場について議論するのは簡単だと思います!)。

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

それがすべてであると言っているわけではありませんMap!それは他のすべてのメソッドを保持でき、保持する必要があります(ただしentrySet、現在は冗長です)。


1
イベントSet <Map.Entry <K、V >>実際
モーリスペリー

3
そうではありません。これは、論理的な結論ではなく、意見(設計FAQに記載)に基づいています。別のアプローチについては、C ++ STL(sgi.com/tech/stl/table_of_contents.html)のコンテナーの設計を見てください。これは、Stepanovの徹底的で優れた分析に基づいています。
beldaz 2013年

回答:


123

以下からのJavaコレクションAPIの設計に関するFAQ

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

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

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

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

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

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

Set<Map.Entry<String,String>>

許可されます:

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

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

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

間違いなくequals()/のhashCode()関係Map.Entryは純粋に重要であると言えるかもしれませんが、それでも問題があります。さらに重要なことに、それは本当に何か価値を加えますか?コーナーケースを調べ始めると、この抽象化が失敗することがあります。

HashSetは実際にはとして実装されておりHashMap、その逆ではないことに注意してください。これは純粋に実装の詳細ですが、それでも興味深いものです。

entrySet()存在する主な理由は、走査を単純化することです。そのため、キーを走査してからキーのルックアップを行う必要はありません。一応の証拠としてそれを取ってはいけないMapあるべきSetエントリの(私見)。


1
更新は非常に説得力がありますが、確かに、によって返されるSetビューentrySet()はをサポートしますがremove、サポートはしませんadd(おそらくスローしますUnsupportedException)。だから私はあなたの要点を見ますが、再びOPの要点も見ます。(私の意見は私自身の回答に述べられているとおりです。)
Enno Shioji

1
Zweiは良い点を挙げています。に起因するこれらすべての複雑化addは、entrySet()ビューと同じように処理できます。一部の操作は許可し、他の操作は許可しません。もちろん、「どんなSetことをサポートしていないのadd?」という自然な反応です。-そうですね、もしそのような振る舞いがで受け入れられるならentrySet()、なぜそれは受け入れられthisないのですか?それは私が、言っていますほとんどが、これは考えたら私のような素晴らしいアイデアではありませんが、私はまだ唯一の良いAPI / OOPのデザインを作るものの私自身の理解を豊かにする場合には、さらに議論の価値があると思う理由ですでに確信させました。
polygenelubricants 2010

3
FAQの引用の最初の段落は面白いです:「私たちは感じている...したがって...意味がほとんどありません」。「感じる」と「感覚」が結論を形作るとは思わない; o)
Peter Wippermann '29

Mapを拡張するためにコレクションを作成できますか?わからない、なぜ作者はCollection延長を考えているのMapですか?
オーバーエクスチェンジ

11

あなたの質問をかなり直接的にカバーする多くの回答を得ましたが、少し後戻りして、質問をもう少し一般的に見ると役立つと思います。つまり、Javaライブラリがどのようにして作成されたのかを具体的に確認するのではなく、そのように作成された理由を確認することです。

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

あなたがダースかそこらの異なる「コレクションのような」ものを見れば、事実上それらの一つ一つは、おそらく、少なくとも一つの他の1と共通の8つのまたは10の特性のようなものを持っています-しかし、あなたは全体で共有されているものを見れば、すべての 1それらのうち、あなたは実質的に何も残されていません。

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

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

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


10

なぜ主観的なの

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:willは関連付け(エントリに相当)をdo:とりますが、値をとります。これらはkeysAndValuesDo:、エントリごとに辞書を繰り返す別の方法を提供します。Add:アソシエーションを取りますが、remove:「抑制されました」:

remove: anObject
self shouldNotImplement 

したがって、確実に実行可能ですが、クラス階層に関して他のいくつかの問題が発生します。

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


3

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


4
これはどのようにSet動作し、Set ない実装しますCollection
Lii

2

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

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

                                Set

                                 |

                              Relation

                                 |

                                Map

                                / \

                             Bag Sequence

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


4
これらの架空のインターフェースのAPIを詳細に確認してください。キーが整数であるシーケンス(別名リスト)が必要かどうかはわかりません。それは大きなパフォーマンスヒットのように聞こえます。
Lawrence Dol、2010

そうです、シーケンスはリストです-したがって、シーケンスはリストを拡張します。これにアクセスできるかどうかはわかりませんが、興味深いかもしれません 。portal.acm.org
citation.cfm?

@SoftwareMonkeyこちらは別のリンクです。最後のリンクは、APIのjavadocへのリンクです。zedlib.sourceforge.net
xagyg

@SoftwareMonkey私は恥ずかしなくここでデザインが私の主な関心事であることを認めます。私はOracle / Sunの教祖がその中から最適化できると想定しています。
xagyg

私は反対する傾向があります。ドメインのメンバー以外の要素をセットに常に追加できない場合(@cletusの回答の例のように)、その「マップはセット」を保持するにはどうすればよいですか。
アインポクルム2015

1

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

同じ引数がために行くaddAll()remove()removeAll()する方法。主な理由は、データが中に保存されている方法の違いであるので、MapCollection。また、Collectionインターフェース実装Iterableインターフェースを呼び出す場合、つまり、.iterator()メソッドのあるインターフェースはイテレータを返す必要があります。イテレータは、に格納された値を反復できるようにする必要がありますCollection。今、そのようなメソッドは何を返すでしょうMapか?キー反復子または値反復子?これも意味がありません。

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


There are ways in which we can iterate over keys and values stores in a Map and that is how it is a part of Collection framework.これのコード例を見せていただけますか?
RamenChef 2016年

それは非常によく説明されました。わかった。ありがとう!
Emir Memic

の実装はadd(Object o)Entry<ClassA, ClassB>オブジェクトを追加することです。A Mapはaと見なすことができますCollection of Tuples
リバノフ2017年

0

まさに、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()としても、誰もがそのメソッドを実装することを忘れず、マップのentrySetを確実に取得できますが、希望する場合はそうではありません実装者が両方のインターフェースを実装している...

私が持ちたくない理由interface Map<K,V> extends Set<Map.Entry<K,V>>は、もっと多くの方法があるからです。結局のところ、それらは異なるものですよね?私がヒットした場合にも非常に実用的に、map.IDEで、私は見たくない.remove(Object obj)、と.remove(Map.Entry<K,V> entry)私は行うことができないのでhit ctrl+space, r, return、それを使って行うこと。


意味的に「マップはMap.Entryのセットである」と主張できれば、関連するメソッドを実装する手間がかかるように思えます。そして、その発言をすることができない場合、そうすることはできません-つまり、それは煩わしさについてであるべきです。
アインポクルム2015

0

Map<K,V>Set<Map.Entry<K,V>>以降は拡張しないでください:

  • 同じに同じキーを持つ異なるを追加することはできませんが、Map.EntryMap
  • あなたはできるさまざまな追加Map.Entry同じに同じキーで複数可Set<Map.Entry>

...これは@cletusの回答の後半部分の要旨の簡潔な説明です。
einpoklum、2015

-2

ストレートでシンプル。コレクションは1つのオブジェクトのみを期待するインターフェイスですが、マップには2つ必要です。

Collection(Object o);
Map<Object,Object>

素敵なトリナド、あなたの答えは常にシンプルで短いです。
サイラム2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.