web-dev-qa-db-fra.com

Pourquoi Java Map étend-il la collection?

J'ai été surpris par le fait que Map<?,?> N'est pas un Collection<?>.

Je pensais que cela aurait beaucoup de sens si elle était déclarée comme telle:

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

Après tout, un Map<K,V> Est une collection de Map.Entry<K,V> , n'est-ce pas?

Y a-t-il donc une bonne raison pour laquelle il n'est pas implémenté en tant que tel?


Merci à Cletus pour la réponse la plus autorisée, mais je me demande toujours pourquoi, si vous pouvez déjà afficher un Map<K,V> En tant que Set<Map.Entries<K,V>> (Via entrySet()), il ne fonctionne pas ' t juste étendre cette interface à la place.

Si un Map est un Collection, quels sont les éléments? La seule réponse raisonnable est "paires de valeurs-clés"

Exactement, interface Map<K,V> extends Set<Map.Entry<K,V>> Serait génial!

mais cela fournit une abstraction Map très limitée (et pas particulièrement utile).

Mais si c'est le cas, pourquoi entrySet est-il spécifié par l'interface? Cela doit être utile d'une manière ou d'une autre (et je pense qu'il est facile de plaider pour cette position!).

Vous ne pouvez pas demander à quelle valeur une clé donnée correspond, ni supprimer l'entrée d'une clé donnée sans savoir à quelle valeur elle correspond.

Je ne dis pas que c'est tout ce qu'il y a à faire à Map! Il peut et devrait garder toutes les autres méthodes (sauf entrySet, qui est redondant maintenant)!

141
polygenelubricants

À partir de la FAQ sur la conception de l'API des collections Java :

Pourquoi Map ne prolonge-t-il pas la collection?

C'était par conception. Nous pensons que les mappages ne sont pas des collections et que les collections ne sont pas des mappages. Ainsi, il est peu logique pour Map d'étendre l'interface Collection (ou vice versa).

Si une carte est une collection, quels sont les éléments? La seule réponse raisonnable est "paires clé-valeur", mais cela fournit une abstraction de carte très limitée (et pas particulièrement utile). Vous ne pouvez pas demander à quelle valeur une clé donnée correspond, ni supprimer l'entrée d'une clé donnée sans savoir à quelle valeur elle correspond.

La collecte pourrait être faite pour étendre Map, mais cela soulève la question: quelles sont les clés? Il n'y a pas de réponse vraiment satisfaisante, et en forcer une mène à une interface contre nature.

Les cartes peuvent être vues comme des collections (de clés, de valeurs ou de paires), et ce fait se reflète dans les trois "opérations d'affichage de collection" sur les cartes (keySet, entrySet et valeurs). Bien qu'il soit, en principe, possible d'afficher une liste en tant que carte mappant des indices à des éléments, cela a la propriété désagréable que la suppression d'un élément de la liste modifie la clé associée à chaque élément avant l'élément supprimé. C'est pourquoi nous n'avons pas d'opération d'affichage de carte sur les listes.

Mise à jour: Je pense que la citation répond à la plupart des questions. Il convient de souligner la partie sur une collection d'entrées qui n'est pas une abstraction particulièrement utile. Par exemple:

Set<Map.Entry<String,String>>

permettrait:

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

(en supposant une méthode entry() qui crée une instance Map.Entry)

Maps nécessitent des clés uniques, cela violerait cela. Ou si vous imposez des clés uniques à un Set d'entrées, ce n'est pas vraiment un Set au sens général. C'est un Set avec d'autres restrictions.

On pourrait peut-être dire que la relation equals()/hashCode() pour Map.Entry Était purement sur la clé, mais même cela a des problèmes. Plus important encore, cela ajoute-t-il vraiment une valeur? Vous pouvez constater que cette abstraction tombe en panne une fois que vous commencez à regarder les cas d'angle.

Il convient de noter que le HashSet est en fait implémenté en tant que HashMap, et non l'inverse. Il s'agit purement d'un détail d'implémentation mais néanmoins intéressant.

La raison principale de l'existence de entrySet() est de simplifier la traversée afin que vous n'ayez pas à parcourir les clés, puis à rechercher la clé. Ne prenez pas cela comme une preuve prima facie qu'un Map devrait être un Set d'entrées (à mon humble avis).

121
cletus

Je suppose que le pourquoi est subjectif.

En C #, je pense que Dictionary étend ou au moins implémente une collection:

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

Dans Pharo Smalltak également:

Collection subclass: #Set
Set subclass: #Dictionary

Mais il existe une asymétrie avec certaines méthodes. Par exemple, collect: will prend l'association (l'équivalent d'une entrée), tandis que do: prenez les valeurs. Ils fournissent une autre méthode keysAndValuesDo: pour itérer le dictionnaire par entrée. Add: prend une association, mais remove: a été "supprimé":

remove: anObject
self shouldNotImplement 

C'est donc définitivement faisable, mais cela conduit à d'autres problèmes concernant la hiérarchie des classes.

Ce qui est mieux, c'est subjectif.

10
ewernli

Bien que vous ayez obtenu un certain nombre de réponses qui couvrent votre question assez directement, je pense qu'il pourrait être utile de prendre un peu de recul et d'examiner la question un peu plus généralement. C'est-à-dire, ne pas regarder spécifiquement comment la bibliothèque Java est écrite, et pourquoi elle est écrite de cette façon.

Le problème ici est que l'héritage ne modélise que n type de point commun. Si vous choisissez deux choses qui semblent toutes les deux comme une collection, vous pouvez probablement choisir 8 ou 10 choses qu'elles ont en commun. Si vous choisissez une autre paire de choses "de type collection", elles auront également 8 ou 10 choses en commun - mais ce ne seront pas les mêmes 8 ou 10 choses comme la première paire .

Si vous regardez une douzaine de choses "similaires à des collections" différentes, pratiquement chacune d'entre elles aura probablement quelque chose comme 8 ou 10 caractéristiques en commun avec au moins une autre - mais si vous regardez ce qui est partagé entre - chaque l'un d'entre eux, vous vous retrouvez avec pratiquement rien.

Il s'agit d'une situation que l'héritage (en particulier l'héritage unique) n'est tout simplement pas bien modélisé. Il n'y a pas de ligne de démarcation nette entre celles qui sont vraiment des collections et celles qui ne le sont pas - mais si vous voulez définir une classe Collection significative, vous êtes obligé d'en laisser certaines. Si vous n'en laissez que quelques-uns, votre classe Collection ne pourra fournir qu'une interface assez clairsemée. Si vous en laissez plus, vous pourrez lui donner une interface plus riche.

Certains prennent également l'option de dire essentiellement: "ce type de collection prend en charge l'opération X, mais vous n'êtes pas autorisé à l'utiliser, en dérivant d'une classe de base qui définit X, mais la tentative d'utilisation de la classe dérivée 'X échoue (par exemple , en lançant une exception).

Cela laisse toujours un problème: presque indépendamment de ce que vous oubliez et de ce que vous mettez, vous allez devoir tracer une ligne dure entre les classes qui sont et celles qui ne le sont pas. Peu importe où vous tracez cette ligne, vous allez vous retrouver avec une division claire, plutôt artificielle, entre certaines choses qui sont assez similaires.

10
Jerry Coffin

La réponse de cletus est bonne, mais je veux ajouter une approche sémantique. Pour combiner les deux n'a aucun sens, pensez au cas où vous ajoutez une paire clé-valeur via l'interface de collecte et la clé existe déjà. L'interface de carte n'autorise qu'une seule valeur associée à la clé. Mais si vous supprimez automatiquement l'entrée existante avec la même clé, la collection a après l'ajout de la même taille qu'avant - très inattendu pour une collection.

3
Mnementh

Les collections Java sont cassées. Il manque une interface, celle de Relation. Par conséquent, Map étend Relation étend Set. Les relations (également appelées multi-cartes) ont des paires nom-valeur uniques. Les cartes (alias "Fonctions"), ont des noms (ou clés) uniques qui bien sûr correspondent aux valeurs. Les séquences étendent les cartes (où chaque clé est un entier> 0). Les sacs (ou multi-ensembles) étendent les cartes (où chaque clé est un élément et chaque valeur est le nombre de fois que l'élément apparaît dans le sac).

Cette structure permettrait l'intersection, l'union, etc., d'une gamme de "collections". Par conséquent, la hiérarchie devrait être:

                                Set

                                 |

                              Relation

                                 |

                                Map

                                / \

                             Bag Sequence

Sun/Oracle/Java ppl - veuillez faire les choses correctement la prochaine fois. Merci.

2
xagyg

Si vous regardez la structure de données respective, vous pouvez facilement deviner pourquoi Map ne fait pas partie de Collection. Chaque Collection stocke une seule valeur tandis qu'un Map stocke une paire clé-valeur. Les méthodes de l'interface Collection sont donc incompatibles avec l'interface Map. Par exemple, dans Collection, nous avons add(Object o). Quelle serait une telle implémentation dans Map. Cela n'a pas de sens d'avoir une telle méthode dans Map. Au lieu de cela, nous avons une méthode put(key,value) dans Map.

Le même argument vaut pour les méthodes addAll(), remove() et removeAll(). La raison principale est donc la différence dans la façon dont les données sont stockées dans Map et Collection. Aussi, si vous vous souvenez de l'interface Collection implémentée Iterable c'est-à-dire que toute interface avec la méthode .iterator() devrait retourner un itérateur qui doit nous permettre d'itérer sur les valeurs stockées dans Collection. Maintenant, que retournerait une telle méthode pour un Map? Itérateur clé ou itérateur Value? Cela n'a pas de sens non plus.

Il existe des façons de parcourir les clés et les magasins de valeurs dans un Map et c'est ainsi qu'il fait partie du framework Collection.

1
Mayur Ingle

Map<K,V> ne doit pas s'étendre Set<Map.Entry<K,V>> puisque:

  • Vous ne pouvez pas ajouter différents Map.Entrys avec la même clé vers le même Map, mais
  • Vous pouvez ajouter différents Map.Entrys avec la même clé vers le même Set<Map.Entry>.
0
einpoklum

Exactement, interface Map<K,V> extends Set<Map.Entry<K,V>> Serait génial!

En fait, si c'était implements Map<K,V>, Set<Map.Entry<K,V>>, Alors j'ai tendance à être d'accord .. Cela semble même naturel. Mais cela ne fonctionne pas très bien, non? Disons que nous avons HashMap implements Map<K,V>, Set<Map.Entry<K,V>, LinkedHashMap implements Map<K,V>, Set<Map.Entry<K,V> Etc ... c'est très bien, mais si vous aviez entrySet(), personne n'oubliera d'implémenter cette méthode, et vous pouvez être sûr que vous pouvez obtenir entrySet pour n'importe quelle carte, alors que vous ne l'êtes pas si vous espérez que l'implémenteur a implémenté les deux interfaces ...

La raison pour laquelle je ne veux pas avoir interface Map<K,V> extends Set<Map.Entry<K,V>> Est simplement parce qu'il y aura plus de méthodes. Et après tout, ce sont des choses différentes, non? Aussi très pratique, si je tape map. Dans IDE, je ne veux pas voir .remove(Object obj) et .remove(Map.Entry<K,V> entry) parce que je ne peux pas faire hit ctrl+space, r, return et en finir.

0
Enno Shioji