Was ist der Unterschied zwischen den HashMap- und Map-Objekten in Java?


348

Was ist der Unterschied zwischen den folgenden Karten, die ich erstelle (in einer anderen Frage haben die Leute scheinbar austauschbar mit ihnen geantwortet und ich frage mich, ob / wie sie sich unterscheiden):

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

Angenommen, Sie implementieren mit HashMap und Mary verwendet Map. Wird es kompiliert?
GilbertS

Antworten:


445

Es gibt keinen Unterschied zwischen den Objekten; Sie haben HashMap<String, Object>in beiden Fällen eine. Es gibt einen Unterschied in der Schnittstelle, die Sie zum Objekt haben. Im ersten Fall ist die Schnittstelle HashMap<String, Object>, im zweiten Fall Map<String, Object>. Das zugrunde liegende Objekt ist jedoch dasselbe.

Der Vorteil der Verwendung Map<String, Object>besteht darin, dass Sie das zugrunde liegende Objekt in eine andere Art von Karte ändern können, ohne Ihren Vertrag mit Code zu brechen, der es verwendet. Wenn Sie dies als deklarieren HashMap<String, Object>, müssen Sie Ihren Vertrag ändern, wenn Sie die zugrunde liegende Implementierung ändern möchten.


Beispiel: Nehmen wir an, ich schreibe diese Klasse:

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...
}

Die Klasse verfügt über einige interne Zuordnungen von Zeichenfolgen-> Objekten, die sie (über Zugriffsmethoden) mit Unterklassen teilt. Nehmen wir an, ich schreibe es zunächst mit HashMaps, weil ich denke, dass dies die geeignete Struktur für das Schreiben der Klasse ist.

Später schreibt Mary Code in Unterklassen. Sie hat etwas , das sie mit den beiden tun muss thingsund moreThings, so natürlich macht sie , dass in einer gemeinsamen Methode, und sie verwendet den gleichen Typ I verwendeten getThings/ getMoreThingsbei der Definition ihrer Methode:

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

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

    // ...more...
}

Später entscheide ich, dass es eigentlich besser ist, wenn ich TreeMapanstelle von HashMapin verwende Foo. Ich aktualisiere Foound wechsle HashMapzu TreeMap. Jetzt wird SpecialFoonicht mehr kompiliert, weil ich den Vertrag gebrochen habe: FooFrüher hieß es HashMaps, aber jetzt TreeMapsstattdessen. Also müssen wir jetzt Abhilfe schaffen SpecialFoo(und so etwas kann sich durch eine Codebasis kräuseln).

Wenn ich nicht einen wirklich guten Grund hatte zu teilen, dass meine Implementierung ein verwendet HashMap(und das passiert), hätte ich deklarieren getThingsund getMoreThingseinfach zurückkehren sollen, Map<String, Object>ohne spezifischer zu sein. In der Tat, abgesehen von einem guten Grund, etwas anderes zu tun, Foosollte ich selbst innerhalb wahrscheinlich erklären thingsund moreThingsals Map, nicht 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...
}

Beachten Sie, wie ich jetzt Map<String, Object>überall verwende, wo ich kann, und nur dann spezifisch bin, wenn ich die tatsächlichen Objekte erstelle.

Wenn ich das getan hätte, hätte Mary dies getan:

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

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

... und das Ändern Foohätte nicht dazu geführt, SpecialFoodass das Kompilieren aufgehört hätte.

Über Schnittstellen (und Basisklassen) können wir nur so viel wie nötig preisgeben und unsere Flexibilität unter Verschluss halten, um gegebenenfalls Änderungen vorzunehmen. Im Allgemeinen möchten wir, dass unsere Referenzen so einfach wie möglich sind. Wenn wir nicht wissen müssen, dass es ein ist HashMap, nennen Sie es einfach ein Map.

Dies ist keine blinde Regel, aber im Allgemeinen ist die Codierung für die allgemeinste Schnittstelle weniger spröde als die Codierung für etwas Spezifischeres. Wenn ich mich daran erinnert hätte, hätte ich keine geschaffen Foo, die Mary zum Scheitern verurteilt hätte SpecialFoo. Wenn Mary sich daran erinnert hätte, hätte sie, obwohl ich es vermasselt Foohabe, ihre private Methode mit Mapstatt deklariert, HashMapund der Vertrag meiner Änderung Foohätte sich nicht auf ihren Code ausgewirkt.

Manchmal kann man das nicht, manchmal muss man spezifisch sein. Aber wenn Sie keinen Grund dazu haben, gehen Sie zur am wenigsten spezifischen Schnittstelle.


56

Map ist eine Schnittstelle, die HashMap implementiert. Der Unterschied besteht darin, dass in der zweiten Implementierung Ihr Verweis auf die HashMap nur die Verwendung von Funktionen erlaubt, die in der Map-Schnittstelle definiert sind, während die erste die Verwendung aller öffentlichen Funktionen in HashMap (einschließlich der Map-Schnittstelle) erlaubt.

Es ist wahrscheinlich sinnvoller, wenn Sie das Tutorial zur Benutzeroberfläche von Sun lesen


Ich gehe davon aus: first = HashMap <String, Object> map = new HashMap <String, Object> ();
OneWorld

Es ist ähnlich wie oft eine Liste als ArrayList implementiert wird
Gerard

26

Geben Sie hier die Bildbeschreibung ein

Map hat die folgenden Implementierungen:

  1. HashMap Map m = new HashMap();

  2. LinkedHashMap Map m = new LinkedHashMap();

  3. Baumkarte Map m = new TreeMap();

  4. WeakHashMap Map m = new WeakHashMap();

Angenommen, Sie haben eine Methode erstellt (dies ist nur ein Pseudocode).

public void HashMap getMap(){
   return map;
}

Angenommen, Ihre Projektanforderungen ändern sich:

  1. Die Methode sollte Karteninhalte zurückgeben - Muss zurückgegeben werden HashMap.
  2. Ändern müssen Rückgabetyp - Die Methode sollte Mapkey die in Auftrag zurückkehren HashMapzu LinkedHashMap.
  3. Ändern müssen Rückgabetyp - Die Methode sollte Mapkey die in sortierter Reihenfolge zurückkehren LinkedHashMapzu TreeMap.

Wenn Ihre Methode bestimmte Klassen anstelle von etwas zurückgibt, das die MapSchnittstelle implementiert , müssen Sie den Rückgabetyp der getMap()Methode jedes Mal ändern .

Wenn Sie jedoch die Polymorphismusfunktion von Java verwenden und nicht bestimmte Klassen zurückgeben, sondern die Schnittstelle verwenden Map, wird die Wiederverwendbarkeit des Codes verbessert und die Auswirkung von Anforderungsänderungen verringert.


17

Ich wollte dies nur als Kommentar zu der akzeptierten Antwort tun, aber es wurde zu funky (ich hasse es, keine Zeilenumbrüche zu haben).

ah, der Unterschied besteht also darin, dass Map im Allgemeinen bestimmte Methoden zugeordnet ist. Es gibt jedoch verschiedene Möglichkeiten, eine Karte zu erstellen, z. B. eine HashMap, und diese verschiedenen Möglichkeiten bieten einzigartige Methoden, über die nicht alle Karten verfügen.

Genau - und Sie möchten immer die allgemeinste Schnittstelle verwenden, die Sie können. Betrachten Sie ArrayList vs LinkedList. Ein großer Unterschied in der Art und Weise, wie Sie sie verwenden, aber wenn Sie "Liste" verwenden, können Sie leicht zwischen ihnen wechseln.

Tatsächlich können Sie die rechte Seite des Initialisierers durch eine dynamischere Anweisung ersetzen. wie wäre es mit so etwas:

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

Auf diese Weise würden Sie, wenn Sie die Sammlung mit einer Einfügungssortierung füllen möchten, eine verknüpfte Liste verwenden (eine Einfügungssortierung in eine Array-Liste ist kriminell). Wenn Sie sie jedoch nicht sortieren müssen und nur anhängen, Sie verwenden eine ArrayList (effizienter für andere Operationen).

Dies ist hier eine ziemlich große Strecke, da Sammlungen nicht das beste Beispiel sind, aber im OO-Design ist eines der wichtigsten Konzepte die Verwendung der Schnittstellenfassade, um auf verschiedene Objekte mit genau demselben Code zuzugreifen.

Bearbeiten Antwort auf Kommentar:

Was Ihren Kartenkommentar unten betrifft, so beschränkt sich Ja, wenn Sie die "Map" -Schnittstelle verwenden, nur auf diese Methoden, es sei denn, Sie setzen die Sammlung von Map in HashMap zurück (was den Zweck VOLLSTÄNDIG zunichte macht).

Oft erstellen Sie ein Objekt und füllen es mit seinem spezifischen Typ (HashMap) in einer Art "create" - oder "initialize" -Methode aus. Diese Methode gibt jedoch eine "Map" zurück, die nicht benötigt wird als HashMap nicht mehr manipuliert.

Wenn Sie übrigens jemals Casting durchführen müssen, verwenden Sie wahrscheinlich die falsche Oberfläche oder Ihr Code ist nicht gut genug strukturiert. Beachten Sie, dass es akzeptabel ist, dass ein Abschnitt Ihres Codes ihn als "HashMap" behandelt, während der andere ihn als "Map" behandelt. Dies sollte jedoch "down" erfolgen. so dass Sie nie gießen.

Beachten Sie auch den halbwegs ordentlichen Aspekt der Rollen, die durch Schnittstellen angezeigt werden. Eine LinkedList macht einen guten Stapel oder eine gute Warteschlange, eine ArrayList macht einen guten Stapel, aber eine schreckliche Warteschlange (wieder würde ein Entfernen eine Verschiebung der gesamten Liste verursachen), so dass LinkedList die Warteschlangenschnittstelle implementiert, ArrayList nicht.


aber in diesem Beispiel bekomme ich nur die Methoden aus der allgemeinen List-Klasse, oder? unabhängig davon, ob ich es zu einer LinkedList () oder einer ArrayList () mache? Es ist nur so, dass wenn ich die Einfügesortierung verwende (was meiner Meinung nach eine Methode für List sein muss, die LinkedList und ArrayList durch Vererbung erhalten), diese auf der LinkedList viel schneller funktioniert.
Tony Stark

Ich denke, was ich suche ist, ob ich Map <string, string> m = new HashMap <string, string> () sage oder nicht. Meine Map m kann die für HashMap spezifischen Methoden verwenden oder nicht. Ich denke es kann nicht?
Tony Stark

ah, warte, nein, meine Map m von oben muss die Methoden von HashMap haben.
Tony Stark

Grundsätzlich ist der einzige Vorteil der Verwendung von Map im Sinne der Benutzeroberfläche, dass ich garantiere, dass jeder Kartentyp in dieser Methode funktioniert, wenn ich eine Methode habe, für die eine Map erforderlich ist. aber wenn ich eine Hashmap verwendet habe, sage ich, dass die Methode nur mit Hashmaps funktioniert. oder anders ausgedrückt, meine Methode verwendet nur Methoden, die in der Map-Klasse definiert sind, aber von den anderen Klassen geerbt werden, die Map erweitern.
Tony Stark

Zusätzlich zu dem oben erwähnten Vorteil, bei dem die Verwendung von List bedeutet, dass ich mich bis zur Laufzeit nicht entscheiden muss, welche Art von Liste ich möchte, während ich, wenn die Schnittstelle nicht vorhanden wäre, vor dem Kompilieren und Ausführen eine auswählen müsste
Tony Stark

12

Wie von TJ Crowder und Adamski festgestellt, bezieht sich eine Referenz auf eine Schnittstelle, die andere auf eine bestimmte Implementierung der Schnittstelle. Laut Joshua Block sollten Sie immer versuchen, Code für Schnittstellen zu erstellen, damit Sie Änderungen an der zugrunde liegenden Implementierung besser verarbeiten können. Wenn HashMap plötzlich nicht mehr für Ihre Lösung geeignet ist und Sie die Map-Implementierung ändern müssen, können Sie die Map dennoch verwenden Schnittstelle und ändern Sie den Instanziierungstyp.


8

In Ihrem zweiten Beispiel ist die "Map" -Referenz vom Typ Map, einer Schnittstelle, die von HashMap(und anderen Arten von Map) implementiert wird . Diese Schnittstelle ist ein Vertrag zu sagen , dass das Objekt abbildet Schlüssel auf Werte und unterstützt verschiedene Operationen (zB put, get). Es sagt nichts über die Implementierung der Map(in diesem Fall a HashMap).

Der zweite Ansatz wird im Allgemeinen bevorzugt, da Sie die spezifische Kartenimplementierung normalerweise nicht Methoden aussetzen möchten, die die MapAPI-Definition oder verwenden.


8

Map ist der statische Kartentyp, während HashMap der dynamische Kartentyp ist. Dies bedeutet, dass der Compiler Ihr Map-Objekt als Map-Objekt behandelt, obwohl es zur Laufzeit auf einen beliebigen Subtyp verweisen kann.

Diese Praxis des Programmierens gegen Schnittstellen anstelle von Implementierungen hat den zusätzlichen Vorteil, dass Sie flexibel bleiben: Sie können beispielsweise den dynamischen Kartentyp zur Laufzeit ersetzen, sofern es sich um einen Untertyp der Karte handelt (z. B. LinkedHashMap), und das Verhalten der Karte ändern die Fliege.

Eine gute Faustregel ist, auf API-Ebene so abstrakt wie möglich zu bleiben: Wenn beispielsweise eine von Ihnen programmierte Methode für Maps arbeiten muss, reicht es aus, einen Parameter anstelle des strengeren (weil weniger abstrakten) HashMap-Typs als Map zu deklarieren . Auf diese Weise kann der Benutzer Ihrer API flexibel festlegen, welche Art von Map-Implementierung er an Ihre Methode übergeben möchte.


4

Ich möchte noch ein bisschen mehr graben, indem ich zu der am besten bewerteten Antwort hinzufüge und viele darüber, die "generischer, besser" betonen.

Mapist der Strukturvertrag, während HashMapeine Implementierung ihre eigenen Methoden zur Bewältigung verschiedener realer Probleme bereitstellt: wie man den Index berechnet, wie groß die Kapazität ist und wie man ihn erhöht, wie man ihn einfügt, wie man den Index eindeutig hält usw.

Schauen wir uns den Quellcode an:

In haben Mapwir die Methode von containsKey(Object key):

boolean containsKey(Object key);

JavaDoc:

boolean java.util.Map.containsValue (Objektwert)

Gibt true zurück, wenn diese Zuordnung einen oder mehrere Schlüssel dem angegebenen Wert zuordnet. Genauer gesagt, gibt true nur dann zurück, wenn diese Zuordnung mindestens eine Zuordnung zu einem vsolchen Wert enthält (value==null ? v==null : value.equals(v)). Diese Operation erfordert wahrscheinlich für die meisten Implementierungen der Kartenschnittstelle eine zeitlineare Kartengröße.

Parameter: Wert

Wert, dessen Anwesenheit in dieser Karte zu verraten ist

Rückgabe: true

Wenn diese Karte einen oder mehrere Schlüssel dem angegebenen zuordnet

valueThrows:

ClassCastException - wenn der Wert für diese Map von einem ungeeigneten Typ ist (optional)

NullPointerException - Wenn der angegebene Wert null ist und diese Zuordnung keine Nullwerte zulässt (optional)

Es erfordert seine Implementierungen, um es zu implementieren, aber das "How to" ist in seiner Freiheit, nur um sicherzustellen, dass es korrekt zurückgibt.

In HashMap:

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

Es stellt sich heraus, dass HashMapHashcode verwendet wird, um zu testen, ob diese Karte den Schlüssel enthält. Es hat also den Vorteil eines Hash-Algorithmus.


3

Sie erstellen die gleichen Karten.

Sie können den Unterschied jedoch ausfüllen, wenn Sie ihn verwenden. Im ersten Fall können Sie spezielle HashMap-Methoden verwenden (aber ich erinnere mich an niemanden, der wirklich nützlich ist), und Sie können ihn als HashMap-Parameter übergeben:

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

...

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

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

3

Map ist eine Schnittstelle und Hashmap ist eine Klasse, die Map Interface implementiert


1

Map ist die Schnittstelle und Hashmap ist die Klasse, die das implementiert.

In dieser Implementierung erstellen Sie also dieselben Objekte


0

HashMap ist eine Implementierung von Map, es ist also ziemlich dasselbe, hat aber die "clone ()" - Methode, wie ich im Referenzhandbuch sehe))


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

Zunächst einmal Mapist eine Schnittstelle , es andere Implementierung hat wie - HashMap, TreeHashMap, LinkedHashMaparbeitet usw. Schnittstelle wie ein Superklasse für die implementierende Klasse. Nach der OOP-Regel ist also jede konkrete Klasse, die implementiert Mapwird, auch eine Map. Das Mittel können wir jeden zuweisen / Put - HashMapTyp Variable auf eine MapVariable vom Typ ohne jede Art von Gießen.

In diesem Fall können wir zuordnen map1zu map2ohne Gießen oder jede verlierende von Daten -

map2 = map1
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.