Warum erweitert Java Map die Sammlung nicht?

146

Ich war überrascht, dass Map<?,?>das kein ist Collection<?>.

Ich dachte, es wäre sehr sinnvoll, wenn es als solches deklariert würde:

public interface Map<K,V> extends Collection<Map.Entry<K,V>>

Immerhin ist a Map<K,V>eine Sammlung von Map.Entry<K,V>, nicht wahr?

Gibt es einen guten Grund, warum es nicht als solches implementiert wird?


Vielen Dank an Cletus für die maßgebliche Antwort, aber ich frage mich immer noch, warum, wenn Sie bereits ein Map<K,V>as Set<Map.Entries<K,V>>(via entrySet()) anzeigen können , diese Schnittstelle nicht einfach erweitert wird.

Wenn a a Mapist Collection, was sind die Elemente? Die einzig vernünftige Antwort lautet "Schlüssel-Wert-Paare".

Genau, interface Map<K,V> extends Set<Map.Entry<K,V>>wäre toll!

Dies bietet jedoch eine sehr begrenzte (und nicht besonders nützliche) MapAbstraktion.

Aber wenn dies der Fall ist, warum wird es dann entrySetvon der Schnittstelle angegeben? Es muss irgendwie nützlich sein (und ich denke, es ist einfach, für diese Position zu argumentieren!).

Sie können weder fragen, welchem ​​Wert ein bestimmter Schlüssel zugeordnet ist, noch den Eintrag für einen bestimmten Schlüssel löschen, ohne zu wissen, welchem ​​Wert er zugeordnet ist.

Ich sage nicht, dass das alles ist, was dazu gehört Map! Es kann und sollte alle anderen Methoden beibehalten (außer entrySet, die jetzt redundant sind)!

Polygenschmierstoffe
quelle
1
Ereignis Set <Map.Entry <K, V >> tatsächlich
Maurice Perry
3
Das tut es einfach nicht. Es basiert auf einer Meinung (wie in den Design-FAQ beschrieben) und ist keine logische Schlussfolgerung. Einen alternativen Ansatz finden Sie im Design von Containern in der C ++ STL ( sgi.com/tech/stl/table_of_contents.html ), das auf Stepanovs gründlicher und brillanter Analyse basiert.
Beldaz

Antworten:

123

Aus den häufig gestellten Fragen zum Java Collections API Design :

Warum erweitert Map die Sammlung nicht?

Dies war beabsichtigt. Wir sind der Meinung, dass Zuordnungen keine Sammlungen sind und Sammlungen keine Zuordnungen. Daher ist es für Map wenig sinnvoll, die Sammlungsschnittstelle zu erweitern (oder umgekehrt).

Wenn eine Karte eine Sammlung ist, welche Elemente gibt es? Die einzig vernünftige Antwort ist "Schlüssel-Wert-Paare", dies bietet jedoch eine sehr begrenzte (und nicht besonders nützliche) Kartenabstraktion. Sie können weder fragen, welchem ​​Wert ein bestimmter Schlüssel zugeordnet ist, noch den Eintrag für einen bestimmten Schlüssel löschen, ohne zu wissen, welchem ​​Wert er zugeordnet ist.

Eine Sammlung könnte gemacht werden, um die Karte zu erweitern, aber dies wirft die Frage auf: Was sind die Schlüssel? Es gibt keine wirklich zufriedenstellende Antwort, und das Erzwingen einer Antwort führt zu einer unnatürlichen Schnittstelle.

Karten können als Sammlungen (von Schlüsseln, Werten oder Paaren) angezeigt werden. Diese Tatsache spiegelt sich in den drei "Sammlungsansichtsoperationen" in Karten wider (keySet, entrySet und Werte). Während es im Prinzip möglich ist, eine Liste als Map-Zuordnungsindizes zu Elementen anzuzeigen, hat dies die böse Eigenschaft, dass das Löschen eines Elements aus der Liste den Schlüssel ändert, der jedem Element vor dem gelöschten Element zugeordnet ist. Aus diesem Grund haben wir keine Kartenansicht in Listen.

Update: Ich denke, das Zitat beantwortet die meisten Fragen. Es lohnt sich, den Teil zu betonen, dass eine Sammlung von Einträgen keine besonders nützliche Abstraktion ist. Beispielsweise:

Set<Map.Entry<String,String>>

würde erlauben:

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

(unter der Annahme einer entry()Methode, die a erstelltMap.Entry Instanz erstellt)

Maps erfordern eindeutige Schlüssel, damit dies verletzt wird. Oder wenn Sie einem SetEintrag eindeutige Schlüssel auferlegen , ist dies Setim Allgemeinen kein wirklicher . Es ist einSet mit weiteren Einschränkungen.

Man könnte wohl die equals()/ hashCode()Beziehung für sagenMap.Entry nur der Schlüssel war, aber selbst das hat Probleme. Was noch wichtiger ist: Fügt es wirklich einen Mehrwert hinzu? Möglicherweise bricht diese Abstraktion zusammen, wenn Sie sich die Eckfälle ansehen.

Es ist erwähnenswert, dass das HashSettatsächlich als implementiert istHashMap , nicht umgekehrt. Dies ist lediglich ein Implementierungsdetail, aber dennoch interessant.

Der Hauptgrund für entrySet()die Existenz besteht darin, das Durchlaufen zu vereinfachen, sodass Sie die Schlüssel nicht durchlaufen und dann den Schlüssel nachschlagen müssen. Nehmen Sie es nicht als Anscheinsbeweis dafür, dass a Mapein SetEintrag sein sollte (imho).

Cletus
quelle
1
Das Update ist sehr überzeugend, aber tatsächlich unterstützt die von zurückgegebene Set-Ansicht entrySet()nicht remove, während sie nicht unterstützt add(wahrscheinlich wirft UnsupportedException). Also ich sehe Ihren Punkt, aber
andererseits
1
Zwei bringt einen guten Punkt vor. All diese Komplikationen addkönnen genauso behandelt werden wie die entrySet()Ansicht: Einige Vorgänge zulassen und andere nicht zulassen. Eine natürliche Antwort lautet natürlich: "Was unterstützt Setdas nicht add?" - entrySet()Nun , wenn ein solches Verhalten akzeptabel ist , warum ist es dann nicht akzeptabel this? Trotzdem bin ich bereits größtenteils davon überzeugt, warum dies keine so großartige Idee ist, wie ich einst dachte, aber ich denke immer noch, dass es einer weiteren Debatte wert ist, schon allein, um mein eigenes Verständnis dafür zu bereichern, was ein gutes API / OOP-Design ausmacht.
Polygenelubricants
3
Der erste Absatz des Zitats aus den FAQ ist lustig: "Wir fühlen ... also ... macht wenig Sinn". Ich fühle nicht, dass "fühlen" und "spüren" eine Schlussfolgerung bilden; o)
Peter Wippermann
Sammlung könnte gemacht werden, um Karte zu erweitern ? Nicht sicher, warum der Autor für eine CollectionVerlängerung denkt Map?
Überaustausch
11

Obwohl Sie eine Reihe von Antworten erhalten haben, die Ihre Frage ziemlich direkt abdecken, halte ich es für nützlich, einen Schritt zurückzutreten und die Frage etwas allgemeiner zu betrachten. Das heißt, nicht speziell darauf zu achten, wie die Java-Bibliothek geschrieben wird, und darauf, warum sie so geschrieben ist.

Das Problem hierbei ist, dass die Vererbung nur eine Art von Gemeinsamkeit modelliert . Wenn Sie zwei Dinge auswählen, die beide "sammlungsartig" erscheinen, können Sie wahrscheinlich 8 oder 10 Dinge auswählen, die sie gemeinsam haben. Wenn Sie ein anderes Paar "sammlungsähnlicher" Dinge auswählen, haben sie auch 8 oder 10 Dinge gemeinsam - aber sie sind nicht die gleichen 8 oder 10 Dinge wie das erste Paar.

Wenn Sie an einem Dutzend aussehen oder so anders „-Kollektion-like“ Dinge, praktisch jeder von ihnen wird wahrscheinlich so etwas wie 8 oder 10 Merkmale gemeinsam mit mindestens einem anderen - aber wenn man sieht , was über geteilt ist jeder ein von ihnen bleibt praktisch nichts übrig.

Dies ist eine Situation, in der die Vererbung (insbesondere die Einzelvererbung) einfach nicht gut modelliert wird. Es gibt keine klare Trennlinie zwischen diesen Sammlungen und welchen nicht - aber wenn Sie eine aussagekräftige Sammlungsklasse definieren möchten, müssen Sie einige davon weglassen. Wenn Sie nur einige davon weglassen, kann Ihre Collection-Klasse nur eine recht spärliche Oberfläche bereitstellen. Wenn Sie mehr weglassen, können Sie ihm eine reichhaltigere Oberfläche geben.

Einige haben auch die Möglichkeit, im Grunde zu sagen: "Diese Art von Sammlung unterstützt Operation X, aber Sie dürfen sie nicht verwenden, indem Sie von einer Basisklasse ableiten, die X definiert, aber der Versuch, die abgeleitete Klasse 'X' zu verwenden, schlägt fehl (z durch Auslösen einer Ausnahme).

Das lässt immer noch ein Problem offen: Fast unabhängig davon, was Sie weglassen und welches Sie einsetzen, müssen Sie eine harte Linie zwischen den Klassen ziehen, die rein und die raus sind. Egal, wo Sie diese Linie ziehen, Sie werden eine klare, eher künstliche Trennung zwischen einigen Dingen haben, die ziemlich ähnlich sind.

Jerry Sarg
quelle
10

Ich denke das Warum ist subjektiv.

In C # wird meiner Meinung Dictionarynach eine Sammlung erweitert oder zumindest implementiert:

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

Auch in Pharo Smalltak:

Collection subclass: #Set
Set subclass: #Dictionary

Bei einigen Methoden besteht jedoch eine Asymmetrie. Zum Beispiel nimmt collect:will eine Assoziation (das Äquivalent eines Eintrags), während do:die Werte genommen werden. Sie bieten eine weitere Methode keysAndValuesDo:, um das Wörterbuch nach Eintrag zu iterieren. Add:nimmt eine Assoziation, wurde aber remove:"unterdrückt":

remove: anObject
self shouldNotImplement 

Es ist also definitiv machbar, führt aber zu einigen anderen Problemen in Bezug auf die Klassenhierarchie.

Was besser ist, ist subjektiv.

ewernli
quelle
3

Die Antwort von Cletus ist gut, aber ich möchte einen semantischen Ansatz hinzufügen. Um beides zu kombinieren, macht es keinen Sinn, sich vorzustellen, dass Sie über die Erfassungsschnittstelle ein Schlüssel-Wert-Paar hinzufügen und der Schlüssel bereits vorhanden ist. Die Map-Schnittstelle erlaubt nur einen Wert, der dem Schlüssel zugeordnet ist. Wenn Sie jedoch den vorhandenen Eintrag automatisch mit demselben Schlüssel entfernen, hat die Sammlung nach dem Hinzufügen dieselbe Größe wie zuvor - für eine Sammlung sehr unerwartet.

Mnementh
quelle
4
Dies ist , wie Setfunktioniert, und Set nicht zu implementieren Collection.
Lii
2

Java-Sammlungen sind kaputt. Es fehlt eine Schnittstelle, die von Relation. Daher erweitert Map Relation Extend Set. Beziehungen (auch Multi-Maps genannt) haben eindeutige Name-Wert-Paare. Karten (auch "Funktionen" genannt) haben eindeutige Namen (oder Schlüssel), die natürlich Werten zugeordnet sind. Sequenzen erweitern Maps (wobei jeder Schlüssel eine Ganzzahl> 0 ist). Taschen (oder mehrere Sätze) erweitern Karten (wobei jeder Schlüssel ein Element ist und jeder Wert die Häufigkeit angibt, mit der das Element in der Tasche erscheint).

Diese Struktur würde die Überschneidung, Vereinigung usw. einer Reihe von "Sammlungen" ermöglichen. Daher sollte die Hierarchie sein:

                                Set

                                 |

                              Relation

                                 |

                                Map

                                / \

                             Bag Sequence

Sun / Oracle / Java ppl - bitte machen Sie es beim nächsten Mal richtig. Vielen Dank.

xagyg
quelle
4
Ich würde gerne die APIs für diese imaginären Schnittstellen detailliert sehen. Ich bin mir nicht sicher, ob ich eine Sequenz (auch bekannt als Liste) haben möchte, bei der der Schlüssel eine Ganzzahl war. das klingt nach einem riesigen Performance-Hit.
Lawrence Dol
Das ist richtig, eine Sequenz ist eine Liste - daher erweitert die Sequenz die Liste. Ich weiß nicht, ob Sie darauf zugreifen können, aber es kann interessant sein portal.acm.org/citation.cfm?id=1838687.1838705
xagyg
@ SoftwareMonkey hier ist ein weiterer Link. Letzter Link ist der Link zum Javadoc der API. zedlib.sourceforge.net
xagyg
@ SoftwareMonkey Ich gebe unverschämt zu, dass Design hier mein Hauptanliegen ist. Ich gehe davon aus, dass die Gurus bei Oracle / Sun das verdammt noch mal optimieren könnten.
Xagyg
Ich bin eher anderer Meinung. Wie kann es sein, dass "Map is-a Set" ist, wenn Sie Ihrem Set nicht immer Nichtmitgliedselemente der Domäne hinzufügen können (wie im Beispiel in der Antwort von @cletus)?
Einpoklum
1

Wenn Sie sich die jeweilige Datenstruktur ansehen, können Sie leicht erraten, warum dies Mapnicht Teil von ist Collection. Jeder Collectionspeichert einen einzelnen Wert, wobei als MapSchlüssel-Wert-Paar gespeichert wird. Daher sind Methoden in der CollectionSchnittstelle für die MapSchnittstelle nicht kompatibel . Zum Beispiel in haben Collectionwir add(Object o). Was wäre eine solche Umsetzung in Map. Es macht keinen Sinn, eine solche Methode zu haben Map. Stattdessen haben wir eine put(key,value)Methode in Map.

Gleiche Argument gilt für addAll(), remove()und removeAll()Methoden. Der Hauptgrund ist also der Unterschied in der Art Mapund Weise, wie Daten in und gespeichert werdenCollection . Auch wenn Sie sich an eine Collectionimplementierte IterableSchnittstelle erinnern .iterator(), sollte jede Schnittstelle mit Methode einen Iterator zurückgeben, der es uns ermöglichen muss, über die in der gespeicherten Werte zu iterieren Collection. Was würde eine solche Methode für eine zurückgeben Map? Schlüsseliterator oder Wertiterator? Das macht auch keinen Sinn.

Es gibt Möglichkeiten, wie wir Schlüssel- und Wertspeicher in a durchlaufen können, und so Mapist es Teil des CollectionFrameworks.

Mayur Ingle
quelle
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.Können Sie hierfür ein Codebeispiel zeigen?
RamenChef
Das wurde sehr gut erklärt. Ich verstehe es jetzt. Vielen Dank!
Emir Memic
Eine Implementierung von add(Object o)wäre das Hinzufügen eines Entry<ClassA, ClassB>Objekts. A Mapkann als a betrachtet werdenCollection of Tuples
LIvanov
0

Genau, interface Map<K,V> extends Set<Map.Entry<K,V>>wäre toll!

Eigentlich, wenn es so wäre implements Map<K,V>, Set<Map.Entry<K,V>>, dann stimme ich eher zu. Es scheint sogar natürlich. Aber das funktioniert nicht sehr gut, oder? Nehmen wir an HashMap implements Map<K,V>, Set<Map.Entry<K,V>, wir haben LinkedHashMap implements Map<K,V>, Set<Map.Entry<K,V>usw. ... das ist alles gut, aber wenn Sie es hättenentrySet() es getan haben, wird niemand vergessen, diese Methode zu implementieren, und Sie können sicher sein, dass Sie entrySet für jede Karte erhalten können, während Sie es nicht sind, wenn Sie hoffen dass der Implementierer beide Schnittstellen implementiert hat ...

Der Grund, den ich nicht haben möchte, interface Map<K,V> extends Set<Map.Entry<K,V>>ist einfach, weil es mehr Methoden geben wird. Und es sind doch verschiedene Dinge, oder? Auch sehr praktisch, wenn ich map.in IDE drücke, will ich nicht sehen .remove(Object obj), und .remove(Map.Entry<K,V> entry)weil ich damit nicht hit ctrl+space, r, returnfertig werden kann.

Enno Shioji
quelle
Es scheint mir, dass man sich die Mühe machen würde, die relevante Methode zu implementieren, wenn man semantisch behaupten könnte, "Eine Karte ist eine Menge auf Map.Entry's"; und wenn man diese Aussage nicht machen kann, dann würde man es nicht tun - dh es sollte sich um die Mühe handeln.
Einpoklum
0

Map<K,V>sollte nicht verlängern Set<Map.Entry<K,V>>seit:

  • Sie können nicht verschiedene Map.Entrys mit demselben Schlüssel zum selben hinzufügen Map, aber
  • Sie können verschiedene Map.Entrys mit demselben Schlüssel zum selben hinzufügen Set<Map.Entry>.
einpoklum
quelle
... dies ist eine prägnante Aussage des Ghist des zweiten Teils von @cletus 'Antwort.
Einpoklum
-2

Geradlinig und einfach. Die Sammlung ist eine Schnittstelle, die nur ein Objekt erwartet, während für die Karte zwei erforderlich sind.

Collection(Object o);
Map<Object,Object>
Trinadh Koya
quelle
Netter Trinad, deine Antworten sind immer einfach und kurz.
Sairam