web-dev-qa-db-fra.com

Quelle est la différence entre les objets HashMap et Map en Java?

Quelle est la différence entre les cartes suivantes que je crée (dans une autre question, les personnes ayant répondu les ont utilisées de manière apparemment interchangeable et je me demande si elles sont différentes, et en quoi):

HashMap<String, Object> map = new HashMap<String, Object>();
Map<String, Object> map = new HashMap<String, Object>();
330
Tony Stark

Il n'y a pas de différence entre les objets; vous avez un HashMap<String, Object> dans les deux cas. Il y a une différence dans le interface vous avez à l'objet. Dans le premier cas, l'interface est HashMap<String, Object>, alors que dans le second c'est Map<String, Object>. Mais l'objet sous-jacent est le même.

L'avantage d'utiliser Map<String, Object> est que vous pouvez changer l'objet sous-jacent pour qu'il soit un type de carte différent sans rompre votre contrat avec le code qui l'utilise. Si vous le déclarez comme HashMap<String, Object>, vous devez modifier votre contrat si vous souhaitez modifier l'implémentation sous-jacente.


Exemple: disons que j'écris cette classe:

class Foo {
    private HashMap<String, Object> things;
    private HashMap<String, Object> moreThings;

    protected HashMap<String, Object> getThings() {
        return this.things;
    }

    protected HashMap<String, Object> getMoreThings() {
        return this.moreThings;
    }

    public Foo() {
        this.things = new HashMap<String, Object>();
        this.moreThings = new HashMap<String, Object>();
    }

    // ...more...
}

La classe possède quelques cartes internes string-> object qu'elle partage (via des méthodes d'accès) avec des sous-classes. Disons que je l'écris avec HashMaps pour commencer car je pense que c'est la structure appropriée à utiliser lors de l'écriture de la classe.

Plus tard, Mary écrit le code en le sous-classant. Elle a quelque chose à faire avec things et moreThings, alors elle l'a naturellement utilisée dans une méthode commune et utilise le même type que celui que j'ai utilisé pour getThings/getMoreThings pour la définir:

class SpecialFoo extends Foo {
    private void doSomething(HashMap<String, Object> t) {
        // ...
    }

    public void whatever() {
        this.doSomething(this.getThings());
        this.doSomething(this.getMoreThings());
    }

    // ...more...
}

Plus tard, je décide qu'en fait, il vaut mieux utiliser TreeMap au lieu de HashMap dans Foo. Je mets à jour Foo, en remplaçant HashMap par TreeMap. Désormais, SpecialFoo ne compile plus, car j’ai rompu le contrat: Foo indiquait qu’il fournissait HashMaps, mais il fournit maintenant TreeMaps. Nous devons donc corriger SpecialFoo maintenant (et ce genre de chose peut se répercuter sur une base de code).

Sauf si j'avais une très bonne raison de partager que mon implémentation utilisait un HashMap (et que cela se produise), ce que j'aurais dû faire était de déclarer getThings et getMoreThings comme renvoyant simplement Map<String, Object> sans être plus spécifique que cela. En fait, à moins d'une bonne raison de faire autre chose, même à l'intérieur de Foo, je devrais probablement déclarer things et moreThings comme Map: et non pas HashMap et ___ TreeMap:

class Foo {
    private Map<String, Object> things;             // <== Changed
    private Map<String, Object> moreThings;         // <== Changed

    protected Map<String, Object> getThings() {     // <== Changed
        return this.things;
    }

    protected Map<String, Object> getMoreThings() { // <== Changed
        return this.moreThings;
    }

    public Foo() {
        this.things = new HashMap<String, Object>();
        this.moreThings = new HashMap<String, Object>();
    }

    // ...more...
}

Notez que j'utilise maintenant Map<String, Object> partout où je le peux, et que je ne suis spécifique que lorsque je crée les objets réels.

Si j'avais fait ça, alors Marie aurait fait ça:

class SpecialFoo extends Foo {
    private void doSomething(Map<String, Object> t) { // <== Changed
        // ...
    }

    public void whatever() {
        this.doSomething(this.getThings());
        this.doSomething(this.getMoreThings());
    }
}

... et changer Foo n'aurait pas obligé SpecialFoo à arrêter la compilation.

Les interfaces (et les classes de base) révèlent niquement dans la mesure nécessaire, en laissant notre marge de manœuvre suffisante pour apporter les modifications appropriées. En général, nous souhaitons que nos références soient aussi élémentaires que possible. Si nous n'avons pas besoin de savoir qu'il s'agit d'un HashMap, appelez-le simplement un Map.

Ce n'est pas une règle aveugle, mais en général, coder pour l'interface la plus générale sera moins fragile que de coder pour quelque chose de plus spécifique. Si je m'en souvenais bien, je n'aurais pas créé un Foo qui configurerait Mary en échec avec SpecialFoo. Si Mary s'était souvenu de cela, alors, même si je m'étais trompé avec Foo, elle aurait déclaré sa méthode privée avec Map au lieu de HashMap et que le contrat de changement de Foo n'aurait pas eu d'incidence sur son code.

Parfois, vous ne pouvez pas faire cela, parfois vous devez être spécifique. Mais à moins que vous n'ayez une raison d'être, penchez-vous vers l'interface la moins spécifique.

399
T.J. Crowder

Map est une interface que HashMap implémente. La différence réside dans le fait que dans la deuxième implémentation, votre référence à HashMap n'autorisera l'utilisation que des fonctions définies dans l'interface Map, tandis que la première autorisera l'utilisation de fonctions publiques dans HashMap (y compris l'interface Map).

Cela vous donnera probablement plus de sens si vous lisez tutoriel sur l'interface de Sun

54
Graphics Noob

enter image description here

Map a les implémentations suivantes:

  1. HashMap Map m = new HashMap();

  2. LinkedHashMap Map m = new LinkedHashMap();

  3. Carte d'arbre Map m = new TreeMap();

  4. WeakHashMap Map m = new WeakHashMap();

Supposons que vous ayez créé une méthode (il s’agit simplement d’un pseudocode).

public void HashMap getMap(){
   return map;
}

Supposons que les exigences de votre projet changent:

  1. La méthode doit renvoyer le contenu de la carte - Besoin de renvoyer HashMap.
  2. La méthode doit renvoyer les clés de carte dans l'ordre d'insertion - Nécessité de changer le type de retour HashMap en LinkedHashMap.
  3. La méthode doit renvoyer les clés de carte dans l'ordre de tri - Nécessité de changer le type de retour LinkedHashMap en TreeMap.

Si votre méthode retourne des classes spécifiques au lieu de quelque chose qui implémente l'interface Map, vous devez changer le type de retour de la méthode getMap() à chaque fois.

Mais si vous utilisez la fonctionnalité de polymorphisme de Java et que, au lieu de renvoyer des classes spécifiques, utilisez l'interface Map, la réutilisation du code est améliorée et l'impact des modifications apportées aux exigences est réduit.

22
atish shimpi

J'allais juste faire cela comme un commentaire sur la réponse acceptée mais cela devenait trop funky (je déteste ne pas avoir de saut de ligne)

ah, donc la différence est qu'en général, certaines méthodes sont associées à Map. mais il existe différentes manières de créer une carte, telle qu'une HashMap, et ces différentes méthodes fournissent des méthodes uniques que ne possèdent pas toutes les cartes.

Exactement - et vous voulez toujours utiliser l'interface la plus générale possible. Considérez ArrayList vs LinkedList. Une différence énorme dans la façon dont vous les utilisez, mais si vous utilisez "Liste", vous pouvez basculer facilement entre eux.

En fait, vous pouvez remplacer le côté droit de l'initialiseur par une instruction plus dynamique. Que diriez-vous quelque chose comme ça:

List collection;
if(keepSorted)
    collection=new LinkedList();
else
    collection=new ArrayList();

De cette façon, si vous voulez remplir la collection avec un tri par insertion, vous utiliseriez une liste chaînée (un tri par insertion dans une liste de tableaux est un crime.) Mais si vous n'avez pas besoin de le trier et de l'ajouter, vous utilisez une ArrayList (Plus efficace pour d’autres opérations).

Il s’agit là d’un domaine assez complexe, car les collections ne sont pas le meilleur exemple. Cependant, dans la conception de OO, l’un des concepts les plus importants consiste à utiliser la façade de l’interface pour accéder à différents objets avec le même code.

Modifier la réponse au commentaire:

En ce qui concerne votre commentaire de carte ci-dessous, l'utilisation de l'interface "Carte" vous limite à utiliser ces méthodes, à moins que vous ne renvoyiez la collection de Map to HashMap (ce qui annule complètement l'objectif).

Souvent, ce que vous ferez est de créer un objet et de le remplir en utilisant son type spécifique (HashMap), dans une sorte de méthode "create" ou "initialize", mais cette méthode retournera un "Map" qui n'a pas besoin d'être manipulé comme un HashMap plus.

En passant, vous utiliserez probablement la mauvaise interface ou votre code ne sera pas assez structuré. Notez qu'il est acceptable qu'une partie de votre code le traite comme un "HashMap", tandis que l'autre le traite comme une "Carte", mais cela devrait s'écouler "vers le bas". de sorte que vous ne lancez jamais.

Notez également l'aspect semi-soigné des rôles indiqué par les interfaces. Une LinkedList constitue une bonne pile ou une bonne file d'attente, un ArrayList une bonne pile, mais une file d'attente épouvantable (là encore, une suppression entraînerait un déplacement de la liste entière), de sorte que LinkedList implémente l'interface Queue, pas ArrayList.

17
Bill K

Comme l'ont noté TJ Crowder et Adamski, une référence est une interface, l'autre une implémentation spécifique. Selon Joshua Block, vous devez toujours essayer de coder les interfaces afin de mieux gérer les modifications apportées à l'implémentation sous-jacente. En d'autres termes, si HashMap n'était soudainement pas idéal pour votre solution et que vous deviez modifier l'implémentation de la carte, vous pouvez toujours utiliser la carte. interface et changer le type d'instanciation.

12
aperkins

Dans votre deuxième exemple, la référence "map" est de type Map, qui est une interface implémentée par HashMap (et d'autres types de Map). Cette interface est un contrat indiquant que l'objet mappe les clés sur des valeurs et prend en charge diverses opérations (par exemple, put, get). Cela dit rien sur l'implémentation de la Map (dans ce cas une HashMap).

La deuxième approche est généralement préférable car vous ne voudriez généralement pas exposer l'implémentation de carte spécifique à des méthodes utilisant la variable Map ou via une définition d'API.

8
Adamski

Map est le type statique de la carte, tandis que HashMap est le type dynamique de la carte. Cela signifie que le compilateur traitera votre objet de carte comme étant de type Carte, même si, à l'exécution, il peut pointer vers n'importe quel sous-type de celui-ci.

Cette pratique consistant à programmer sur des interfaces plutôt que sur des implémentations présente l’avantage supplémentaire de rester flexible: vous pouvez par exemple remplacer le type dynamique de carte au moment de l’exécution, à condition qu’il s’agisse d’un sous-type de Map (par exemple, LinkedHashMap), et modifier le comportement de la carte le même jour. la mouche.

Une bonne règle consiste à rester aussi abstrait que possible au niveau de l'API: si, par exemple, une méthode que vous programmez doit fonctionner sur des cartes, il suffit alors de déclarer un paramètre en tant que Map au lieu du type HashMap le plus strict (car moins abstrait) . De cette façon, le consommateur de votre API peut faire preuve de souplesse quant au type d'implémentation de carte qu'il souhaite transmettre à votre méthode.

8
Matthias

Vous créez les mêmes cartes.

Mais vous pouvez combler la différence quand vous l'utiliserez. Dans le premier cas, vous pourrez utiliser des méthodes spéciales HashMap (mais je ne me souviens de personne vraiment utile), et vous pourrez le transmettre en tant que paramètre HashMap:

public void foo (HashMap<String, Object) { ... }

...

HashMap<String, Object> m1 = ...;
Map<String, Object> m2 = ...;

foo (m1);
foo ((HashMap<String, Object>)m2); 
3
Roman

En ajoutant à la réponse la plus votée et aux nombreuses réponses ci-dessus soulignant le "plus générique, le meilleur", j'aimerais creuser un peu plus.

Map est le contrat de structure tandis que HashMap est une implémentation fournissant ses propres méthodes pour traiter différents problèmes réels: comment calculer un index, quelle est sa capacité et comment l'incrémenter, comment l'insérer, comment garder l'index unique, etc.

Regardons dans le code source:

Dans Map nous avons la méthode de containsKey(Object key):

boolean containsKey(Object key);

JavaDoc:

boolean Java.util.Map.containsValue (valeur d'objet)

Renvoie true si cette carte mappe une ou plusieurs clés sur la valeur spécifiée. Plus formellement, renvoie vrai si et seulement si cette carte contient au moins une correspondance avec une valeur v telle que (value==null ? v==null : value.equals(v)). Cette opération nécessitera probablement une durée linéaire dans la taille de la carte pour la plupart des implémentations de l'interface Map.

Paramètres: valeur

valeur dont la présence sur cette carte est à craindre

Retours: true

si cette carte mappe une ou plusieurs clés sur le

valueThrows:

ClassCastException - si la valeur est d'un type inapproprié pour cette carte (facultatif)

NullPointerException - si la valeur spécifiée est null et que cette carte n'autorise pas les valeurs null (facultatif)

Il faut que ses implémentations le mettent en œuvre, mais le "comment" est à sa liberté, seulement pour s'assurer qu'il retourne correctement.

Dans HashMap:

public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
}

Il s’avère que HashMap utilise hashcode pour vérifier si cette carte contient la clé. Donc, il a l'avantage de l'algorithme de hachage.

2
WesternGun

Map est interface et Hashmap est une classe qui implémente Map Interface.

2
user6623857

Map est l'interface et Hashmap est la classe qui l'implémente.

Donc, dans cette implémentation, vous créez les mêmes objets

1
Diego Dias

HashMap est une implémentation de Map donc c'est à peu près la même chose mais a la méthode "clone ()" comme je vois dans le guide de référence))

0
kolyaseg
HashMap<String, Object> map1 = new HashMap<String, Object>();
Map<String, Object> map2 = new HashMap<String, Object>();  

Tout d'abord, Map est une interface dont l'implémentation est différente, telle que - HashMap, TreeHashMap, LinkedHashMap etc. L'interface fonctionne comme une super-classe pour la classe implémentée. Donc, selon la règle de POO, toute classe concrète implémentant Map est également un Map. Cela signifie que nous pouvons affecter/mettre n'importe quelle variable de type HashMap à une variable de type Map sans aucun type de transtypage.

Dans ce cas, nous pouvons affecter map1 à map2 sans transtypage ni perte de données -

map2 = map1
0
Razib