Qual è la differenza tra gli oggetti HashMap e Map in Java?


349

Qual è la differenza tra le seguenti mappe che creo (in un'altra domanda, le persone hanno risposto usandole apparentemente in modo intercambiabile e mi chiedo se / come sono diverse):

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

Supponiamo di implementare usando HashMap e Mary usa Map. Si compilerà?
GilbertS

Risposte:


446

Non c'è differenza tra gli oggetti; hai un HashMap<String, Object>in entrambi i casi. C'è una differenza nell'interfaccia che hai con l'oggetto. Nel primo caso, l'interfaccia è HashMap<String, Object>, mentre nel secondo è Map<String, Object>. Ma l'oggetto sottostante è lo stesso.

Il vantaggio dell'utilizzo Map<String, Object>è che puoi modificare l'oggetto sottostante in un diverso tipo di mappa senza rompere il contratto con qualsiasi codice che lo utilizza. Se lo dichiari come HashMap<String, Object>, devi modificare il contratto se desideri cambiare l'implementazione sottostante.


Esempio: diciamo che scrivo questa 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 ha un paio di mappe interne di string-> object che condivide (tramite metodi accessor) con sottoclassi. Diciamo che lo scrivo con HashMaps all'inizio perché penso che sia la struttura appropriata da usare quando si scrive la classe.

Più tardi, Mary scrive il codice subclassandolo. Ha qualcosa che deve fare con entrambi thingse moreThings, quindi, naturalmente, lo mette in un metodo comune e usa lo stesso tipo che ho usato su getThings/ getMoreThingsdurante la definizione del suo metodo:

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

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

    // ...more...
}

Più tardi, decido che in realtà, è meglio se uso TreeMapinvece di HashMapin Foo. Aggiornamento Foo, cambiando HashMapin TreeMap. Ora, SpecialFoonon compilare più, perché ho rotto il contratto: era Foosolito dire che forniva HashMaps, ma ora TreeMapsinvece fornisce . Quindi ora dobbiamo risolvere SpecialFoo(e questo genere di cose può incresparsi in una base di codice).

A meno che non avessi una buona ragione per condividere che la mia implementazione stava usando un HashMap(e ciò accade), ciò che avrei dovuto fare era dichiarare getThingse getMoreThingssemplicemente tornare Map<String, Object>senza essere più specifico di così. In effetti, escludendo una buona ragione per fare qualcos'altro, anche all'interno Foodovrei probabilmente dichiarare thingse moreThingscome 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...
}

Nota come sto usando Map<String, Object>tutto il possibile, essendo specifico solo quando creo gli oggetti reali.

Se l'avessi fatto, allora Mary avrebbe fatto questo:

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

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

... e il cambiamento Foonon avrebbe SpecialFooimpedito la compilazione.

Le interfacce (e le classi di base) ci rivelano solo quanto è necessario , mantenendo la nostra flessibilità sotto le coperte per apportare le modifiche appropriate. In generale, vogliamo che i nostri riferimenti siano il più basilari possibile. Se non abbiamo bisogno di sapere che è un HashMap, chiamalo semplicemente un Map.

Questa non è una regola cieca, ma in generale, la codifica per l'interfaccia più generale sarà meno fragile della codifica per qualcosa di più specifico. Se me lo fossi ricordato, non avrei creato un FooMary con cui Mary avrebbe fallito SpecialFoo. Se Mary lo avesse ricordato, allora anche se avessi sbagliato Foo, avrebbe dichiarato il suo metodo privato Mapinvece di HashMape il mio Foocontratto di modifica non avrebbe avuto alcun impatto sul suo codice.

A volte non puoi farlo, a volte devi essere specifico. Ma a meno che tu non abbia un motivo per esserlo, vai verso l'interfaccia meno specifica.


56

Map è un'interfaccia implementata da HashMap . La differenza è che nella seconda implementazione il tuo riferimento a HashMap consentirà solo l'uso di funzioni definite nell'interfaccia di Map, mentre il primo consentirà l'uso di qualsiasi funzione pubblica in HashMap (che include l'interfaccia di Map).

Probabilmente avrà più senso leggere il tutorial sull'interfaccia di Sun.


Presumo: first = HashMap <String, Object> map = new HashMap <String, Object> ();
OneWorld,

È simile alla frequenza con cui un Elenco viene implementato come ArrayList
Gerard,

26

inserisci qui la descrizione dell'immagine

Mappa ha le seguenti implementazioni:

  1. HashMap Map m = new HashMap();

  2. LinkedHashMap Map m = new LinkedHashMap();

  3. Mappa dell'albero Map m = new TreeMap();

  4. WeakHashMap Map m = new WeakHashMap();

Supponiamo di aver creato un metodo (questo è solo pseudocodice).

public void HashMap getMap(){
   return map;
}

Supponiamo che i requisiti del progetto cambino:

  1. Il metodo dovrebbe restituire il contenuto della mappa - È necessario restituirlo HashMap.
  2. Il metodo dovrebbe restituire le chiavi della mappa nell'ordine di inserimento - È necessario modificare il tipo di ritorno HashMapin LinkedHashMap.
  3. Il metodo dovrebbe restituire le chiavi della mappa in ordine ordinato - È necessario modificare il tipo di ritorno LinkedHashMapin TreeMap.

Se il tuo metodo restituisce classi specifiche invece di qualcosa che implementa l' Mapinterfaccia, devi cambiare il tipo di getMap()metodo di ritorno ogni volta.

Ma se si utilizza la funzionalità polimorfismo di Java e invece di restituire classi specifiche, utilizzare l'interfaccia Map, migliora la riusabilità del codice e riduce l'impatto delle modifiche ai requisiti.


17

Stavo per fare questo come un commento sulla risposta accettata, ma è diventato troppo funky (odio non avere interruzioni di riga)

ah, quindi la differenza è che in generale, Map ha alcuni metodi associati. ma ci sono modi diversi o la creazione di una mappa, come una HashMap, e questi modi diversi forniscono metodi unici che non tutte le mappe hanno.

Esatto - e vuoi sempre usare l'interfaccia più generale che puoi. Considera ArrayList vs LinkedList. Enorme differenza nel modo in cui li usi, ma se usi "Elenco" puoi passare facilmente da uno all'altro.

In effetti, è possibile sostituire il lato destro dell'inizializzatore con un'istruzione più dinamica. che ne dici di qualcosa del genere:

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

In questo modo se riempirai la raccolta con un ordinamento di inserzione, utilizzeresti un elenco collegato (un ordinamento di inserzione in un elenco di array è criminale). Ma se non hai bisogno di mantenerlo ordinato e stai solo aggiungendo, si utilizza un ArrayList (più efficiente per altre operazioni).

Questo è un tratto piuttosto grande qui perché le collezioni non sono il miglior esempio, ma nella progettazione di OO uno dei concetti più importanti è usare la facciata dell'interfaccia per accedere a diversi oggetti con lo stesso identico codice.

Modifica rispondendo al commento:

Per quanto riguarda il tuo commento sulla mappa di seguito, Sì utilizzando l'interfaccia "Mappa" ti limita solo a quei metodi a meno che non restituisca la raccolta da Mappa a HashMap (che COMPLETAMENTE vanifica lo scopo).

Spesso quello che farai è creare un oggetto e compilarlo usando il suo tipo specifico (HashMap), in qualche tipo di metodo "crea" o "inizializza", ma quel metodo restituirà una "Mappa" che non ha bisogno di essere manipolato più come HashMap.

Se devi comunque trasmettere, probabilmente stai usando l'interfaccia sbagliata o il tuo codice non è strutturato abbastanza bene. Nota che è accettabile che una sezione del tuo codice lo tratti come una "HashMap" mentre l'altra lo tratta come una "Mappa", ma questo dovrebbe scorrere "verso il basso". in modo da non lanciare mai.

Notare anche l'aspetto semi-ordinato dei ruoli indicati dalle interfacce. Una LinkedList crea una buona pila o coda, una ArrayList fa una buona pila ma una coda orribile (di nuovo, una rimozione causerebbe uno spostamento dell'intero elenco), quindi LinkedList implementa l'interfaccia Queue, ArrayList no.


ma in questo esempio, ottengo solo i metodi dalla classe List generale, giusto? indipendentemente dal fatto che lo renda un LinkedList () o un ArrayList ()? è solo che se uso un ordinamento di inserzione (che immagino debba essere un metodo per List che LinkedList e ArrayList ottengono per eredità) funziona molto più velocemente su LinkedList?
Tony Stark,

suppongo che quello che sto cercando sia se dico Map <string, string> m = new HashMap <string, string> () my Map m può usare i metodi specifici di HashMap oppure no. Sto pensando che non possa?
Tony Stark,

ah, aspetta, no, la mia mappa m dall'alto deve avere i metodi di HashMap.
Tony Stark,

quindi sostanzialmente l'unico vantaggio dell'uso di Map in "senso dell'interfaccia" è che se ho un metodo che richiede una mappa, sto garantendo che qualsiasi tipo di mappa funzionerà con questo metodo. ma se ho usato un hashmap, sto dicendo che il metodo funziona solo con hashmaps. o, in altre parole, il mio metodo utilizza solo metodi definiti nella classe Map ma ereditati dalle altre classi che estendono Map.
Tony Stark,

oltre al vantaggio che hai menzionato sopra, dove usare List significa che non ho bisogno di decidere quale tipo di List voglio fino al runtime, mentre se l'interfaccia non esistesse dovrei sceglierne uno prima di compilare ed eseguire
Tony Stark,

12

Come osservato da TJ Crowder e Adamski, un riferimento è a un'interfaccia, l'altro a un'implementazione specifica dell'interfaccia. Secondo Joshua Block, dovresti sempre provare a programmare le interfacce, per permetterti di gestire meglio le modifiche all'implementazione sottostante - cioè se improvvisamente HashMap non era l'ideale per la tua soluzione e dovevi cambiare l'implementazione della mappa, puoi comunque usare la Mappa interfaccia e modificare il tipo di istanza.


8

Nel tuo secondo esempio il riferimento "map" è di tipo Map, che è un'interfaccia implementata da HashMap(e altri tipi di Map). Questa interfaccia è un contratto di dire che l'oggetto associa chiavi a valori e supporta varie operazioni (ad esempio put, get). Non dice nulla sull'implementazione di Map(in questo caso a HashMap).

Il secondo approccio è generalmente preferito in quanto in genere non si vorrebbe esporre l'implementazione della mappa specifica a metodi che utilizzano la Mapdefinizione API o tramite.


8

Mappa è il tipo statico di mappa, mentre HashMap è il tipo dinamico di mappa. Ciò significa che il compilatore tratterà il tuo oggetto mappa come uno di tipo Mappa, anche se in fase di esecuzione può indicare qualsiasi sottotipo di esso.

Questa pratica di programmazione rispetto alle interfacce anziché alle implementazioni ha l'ulteriore vantaggio di rimanere flessibile: ad esempio è possibile sostituire il tipo dinamico di mappa in fase di esecuzione, purché sia ​​un sottotipo di Mappa (ad esempio LinkedHashMap) e modificare il comportamento della mappa su La mosca.

Una buona regola empirica è quella di rimanere il più astratto possibile a livello di API: se ad esempio un metodo che stai programmando deve funzionare su mappe, è sufficiente dichiarare un parametro come Mappa invece del tipo HashMap più rigoroso (perché meno astratto) . In questo modo, il consumatore della tua API può essere flessibile su quale tipo di implementazione della Mappa desidera passare al tuo metodo.


4

Aggiungendo alla risposta più votata e molte sopra sottolineando il "più generico, migliore", vorrei scavare un po 'di più.

Mapè il contratto di struttura mentre HashMapè un'implementazione che fornisce i propri metodi per affrontare diversi problemi reali: come calcolare l'indice, qual è la capacità e come incrementarlo, come inserire, come mantenere unico l'indice, ecc.

Diamo un'occhiata al codice sorgente:

In Mapabbiamo il metodo di containsKey(Object key):

boolean containsKey(Object key);

JavaDoc:

boolean java.util.Map.containsValue (valore oggetto)

Restituisce vero se questa mappa associa una o più chiavi al valore specificato. Più formalmente, restituisce true se e solo se questa mappa contiene almeno un mapping a un valore vtale (value==null ? v==null : value.equals(v)). Questa operazione richiederà probabilmente un tempo lineare nella dimensione della mappa per la maggior parte delle implementazioni dell'interfaccia della Mappa.

Parametri: valore

valore la cui presenza in questa mappa è da scommettere

Returns: true

se questa mappa associa una o più chiavi a quelle specificate

valueThrows:

ClassCastException - se il valore è di tipo inappropriato per questa mappa (facoltativo)

NullPointerException - se il valore specificato è null e questa mappa non consente valori null (facoltativo)

Richiede le sue implementazioni per implementarlo, ma il "come fare" è alla sua libertà, solo per garantire che ritorni correttamente.

In HashMap:

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

Si scopre che HashMaputilizza l'hashcode per verificare se questa mappa contiene la chiave. Quindi ha il vantaggio dell'algoritmo hash.


3

Si creano le stesse mappe.

Ma puoi colmare la differenza quando la userai. Con il primo caso sarai in grado di utilizzare metodi HashMap speciali (ma non ricordo nessuno davvero utile) e sarai in grado di passarlo come parametro HashMap:

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

...

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

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

3

Map è interfaccia e Hashmap è una classe che implementa Map Interface


1

Map è l'interfaccia e Hashmap è la classe che lo implementa.

Quindi in questa implementazione crei gli stessi oggetti


0

HashMap è un'implementazione di Map, quindi è abbastanza la stessa ma ha il metodo "clone ()" come vedo nella guida di riferimento))


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

Prima di tutto Mapè un'interfaccia che ha implementazione diverso, come - HashMap, TreeHashMap, LinkedHashMapecc interfaccia funziona come un super classe per classe che implementa. Quindi secondo la regola di OOP ogni classe concreta che implementa Mapè Mapanche una . Ciò significa che possiamo assegnare / inserire qualsiasi HashMapvariabile di tipo in una Mapvariabile di tipo senza alcun tipo di casting.

In questo caso possiamo assegnare map1ai map2senza fusione o nulla perdere dei dati -

map2 = map1
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.