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

349

Quelle est la différence entre les cartes suivantes que je crée (dans une autre question, les gens ont répondu en les utilisant de manière interchangeable et je me demande si / comment elles sont différentes):

HashMap<String, Object> map = new HashMap<String, Object>();
Map<String, Object> map = new HashMap<String, Object>();
Tony Stark
la source
Supposons que vous implémentez à l'aide de HashMap et que Mary utilise Map. Compilera-t-il?
GilbertS

Réponses:

446

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

L'avantage de l'utilisation Map<String, Object>est que vous pouvez changer l'objet sous-jacent en un autre type de carte 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 a quelques cartes internes de string-> object qu'elle partage (via des méthodes d'accesseur) avec des sous-classes. Disons que je l'écris avec HashMaps pour commencer parce que je pense que c'est la structure appropriée à utiliser lors de l'écriture de la classe.

Plus tard, Mary écrit du code le sous-classant. Elle a quelque chose à faire avec les deux thingset moreThings, donc naturellement, elle met cela dans une méthode commune, et elle utilise le même type que j'ai utilisé sur getThings/ getMoreThingslors de la définition de sa méthode:

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, c'est mieux si j'utilise à la TreeMapplace de HashMapin Foo. Je mets à jour Foo, en changeant HashMappour TreeMap. Maintenant, SpecialFoone compile plus, parce que j'ai rompu le contrat: je Foodisais qu'il fournissait des HashMaps, mais maintenant il fournit à la TreeMapsplace. Nous devons donc corriger SpecialFoomaintenant (et ce genre de chose peut se répercuter sur une base de code).

À moins d'avoir une très bonne raison de partager que mon implémentation utilisait un HashMap(et cela se produit), ce que j'aurais dû faire était de déclarer getThingset getMoreThingsde simplement revenir Map<String, Object>sans être plus précis que cela. En fait, sauf une bonne raison de faire autre chose, même à l'intérieur, Fooje devrais probablement déclarer thingset moreThingscomme Map, non HashMap/ 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 comment j'utilise maintenant Map<String, Object>partout où je peux, en n'étant spécifique que lorsque je crée les objets réels.

Si j'avais fait ça, alors Mary 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 Foon'aurait pas fait SpecialFooarrêter la compilation.

Les interfaces (et les classes de base) nous permettent de révéler tout ce qui est nécessaire , en gardant notre flexibilité sous les couvertures pour apporter les modifications appropriées. En général, nous voulons que nos références soient aussi simples que possible. Si nous n'avons pas besoin de savoir que c'est un HashMap, appelez-le simplement a Map.

Ce n'est pas une règle aveugle, mais en général, le codage vers l'interface la plus générale sera moins fragile que le codage vers quelque chose de plus spécifique. Si je m'en étais souvenu, je n'aurais pas créé un Fooqui aurait mis Mary en échec SpecialFoo. Si Mary s'était souvenue de cela, alors même si j'avais gâché Foo, elle aurait déclaré sa méthode privée avec Mapau lieu de HashMapet mon Foocontrat de changement n'aurait pas eu d'impact sur son code.

Parfois, vous ne pouvez pas faire cela, parfois vous devez être précis. Mais à moins que vous n'ayez une raison de l'être, optez pour l'interface la moins spécifique.

TJ Crowder
la source
56

Map est une interface implémentée par HashMap . La différence est que dans la deuxième implémentation, votre référence à HashMap autorisera uniquement l'utilisation des fonctions définies dans l'interface Map, tandis que la première autorisera l'utilisation de toutes les fonctions publiques dans HashMap (qui inclut l'interface Map).

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

Graphics Noob
la source
Je suppose que: first = HashMap <String, Object> map = new HashMap <String, Object> ();
OneWorld
Il est similaire à la fréquence à laquelle une liste est implémentée en tant que ArrayList
Gerard
26

entrez la description de l'image ici

La carte 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 (ce n'est qu'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 revenir HashMap.
  2. La méthode doit renvoyer les clés de carte dans l'ordre d'insertion - Besoin de changer le type de retour HashMapen LinkedHashMap.
  3. La méthode doit renvoyer les clés de carte dans l'ordre trié - Vous devez remplacer le type de retour LinkedHashMappar TreeMap.

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

Mais si vous utilisez la fonction de polymorphisme de Java, et au lieu de renvoyer des classes spécifiques, utilisez l'interface Map, cela améliore la réutilisabilité du code et réduit l'impact des changements d'exigences.

atish shimpi
la source
17

J'allais juste faire ça comme un commentaire sur la réponse acceptée mais c'est devenu trop funky (je déteste ne pas avoir de sauts de ligne)

ah, donc la différence est qu'en général, Map a certaines méthodes associées. mais il existe différentes façons de créer une carte, comme un HashMap, et ces différentes façons fournissent des méthodes uniques que toutes les cartes n'ont pas.

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

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 allez remplir la collection avec un tri par insertion, vous utiliseriez une liste liée (un tri par insertion dans une liste de tableaux est criminel.) Mais si vous n'avez pas besoin de le garder trié et que vous ajoutez simplement, vous utilisez une ArrayList (plus efficace pour d'autres opérations).

C'est un tronçon assez important ici parce que les collections ne sont pas le meilleur exemple, mais dans la conception OO, l'un des concepts les plus importants utilise la façade de l'interface pour accéder à différents objets avec le même code exact.

Modifier la réponse au commentaire:

En ce qui concerne votre commentaire de carte ci-dessous, Oui en utilisant l'interface "Carte" vous limite à ces méthodes uniquement, sauf si vous convertissez la collection de Map en HashMap (ce qui va COMPLÈTEMENT à l'encontre du but).

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

Si jamais vous devez lancer un casting, vous utilisez probablement la mauvaise interface ou votre code n'est pas assez bien structuré. Notez qu'il est acceptable qu'une section de votre code le traite comme un "HashMap" tandis que l'autre le traite comme une "carte", mais cela devrait "descendre". de sorte que vous ne lancez jamais.

Notez également l'aspect semi-soigné des rôles indiqué par les interfaces. Un LinkedList fait une bonne pile ou file d'attente, un ArrayList fait une bonne pile mais une file d'attente horrible (encore une fois, une suppression entraînerait un décalage de la liste entière) donc LinkedList implémente l'interface Queue, ArrayList non.

Bill K
la source
mais dans cet exemple, je reçois uniquement les méthodes de la classe List générale, non? que j'en fasse une LinkedList () ou une ArrayList ()? c'est juste que si j'utilise le tri par insertion (qui, j'imagine, doit être une méthode pour List que LinkedList et ArrayList obtiennent par héritage), cela fonctionne-t-il beaucoup plus rapidement sur LinkedList?
Tony Stark
je suppose que ce que je cherche, c'est si oui ou non quand je dis Map <chaîne, chaîne> m = nouveau HashMap <chaîne, chaîne> () ma carte m peut utiliser les méthodes spécifiques à HashMap, ou non. Je pense que ça ne peut pas?
Tony Stark
ah, attendez, non, ma carte m d'en haut doit avoir les méthodes de HashMap.
Tony Stark
Donc, fondamentalement, le seul avantage d'utiliser Map dans le «sens de l'interface» est que si j'ai une méthode qui nécessite une carte, je garantis que tout type de carte fonctionnera dans cette méthode. mais si j'ai utilisé un hashmap, je dis que la méthode ne fonctionne qu'avec des hashmaps. ou, autrement dit, ma méthode utilise uniquement des méthodes définies dans la classe Map mais héritées par les autres classes qui étendent Map.
Tony Stark
en plus de l'avantage que vous avez mentionné ci-dessus, où l'utilisation de List signifie que je n'ai pas besoin de décider quel type de liste je veux avant l'exécution, alors que si la chose d'interface n'existait pas, je devrais en choisir une avant de compiler et d'exécuter
Tony Stark
12

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

aperkins
la source
8

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 disant que l'objet mappe les clés aux valeurs et prend en charge diverses opérations (par exemple put, get). Il ne dit rien sur la mise en œuvre de la Map(dans ce cas a HashMap).

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

Adamski
la source
8

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

Cette pratique de programmation contre des interfaces au lieu d'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, tant qu'il s'agit d'un sous-type de carte (par exemple LinkedHashMap), et modifier le comportement de la carte sur la mouche.

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

Matthias
la source
4

En ajoutant à la réponse la plus votée et à plusieurs réponses ci-dessus soulignant le "plus générique, mieux", je voudrais creuser un peu plus.

Mapest le contrat de structure tandis HashMapqu'une implémentation fournit ses propres méthodes pour traiter différents problèmes réels: comment calculer l'indice, quelle est la capacité et comment l'incrémenter, comment l'insérer, comment garder l'index unique, etc.

Regardons le code source:

En Mapnous avons la méthode de containsKey(Object key):

boolean containsKey(Object key);

JavaDoc:

booléen java.util.Map.containsValue (valeur d'objet)

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

Paramètres: valeur

valeur dont la présence sur cette carte doit être vérifiée

Renvoie: vrai

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

valueThrows:

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

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

Il nécessite ses implémentations pour l'implémenter, mais le "comment faire" 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 HashMaputilise hashcode pour tester si cette carte contient la clé. Il a donc l'avantage de l'algorithme de hachage.

WesternGun
la source
3

Vous créez les mêmes cartes.

Mais vous pouvez combler la différence lorsque vous l'utiliserez. Avec le premier cas, vous pourrez utiliser des méthodes HashMap spéciales (mais je ne me souviens de personne vraiment utile), et vous pourrez le passer 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); 
romain
la source
3

La carte est l'interface et Hashmap est une classe qui implémente l'interface de la carte


la source
1

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

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

Diego Dias
la source
0

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

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

Tout d'abord Mapest une interface différente qu'elle a mise en œuvre comme - HashMap, TreeHashMap, LinkedHashMapetc. Interface fonctionne comme une classe super pour la classe d' implémentation. Ainsi, selon la règle de la POO, toute classe concrète qui implémente Mapest Mapégalement un . Cela signifie que nous pouvons affecter / mettre n'importe quelle HashMapvariable de type à une Mapvariable de type sans aucun type de transtypage.

Dans ce cas, nous pouvons attribuer map1à map2sans casting ni perte de données -

map2 = map1
Razib
la source