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

Qual è la differenza tra le seguenti mappe che creo (in un’altra domanda, le persone hanno risposto usando le stesse apparentemente in modo intercambiabile e mi chiedo se / come siano differenti):

HashMap map = new HashMap(); Map map = new HashMap(); 

Non c’è differenza tra gli oggetti; hai una HashMap in entrambi i casi. C’è una differenza nell’interfaccia che hai sull’object. Nel primo caso, l’interfaccia è HashMap , mentre nel secondo è Map . Ma l’object sottostante è lo stesso.

Il vantaggio di usare Map è che puoi cambiare l’object sottostante in modo che sia un diverso tipo di mappa senza violare il tuo contratto con qualsiasi codice che lo sta usando. Se lo dichiari come HashMap , devi cambiare il tuo contratto se vuoi cambiare l’implementazione sottostante.


Esempio: diciamo che scrivo questa class:

 class Foo { private HashMap things; private HashMap moreThings; protected HashMap getThings() { return this.things; } protected HashMap getMoreThings() { return this.moreThings; } public Foo() { this.things = new HashMap(); this.moreThings = new HashMap(); } // ...more... } 

La class ha un paio di mappe interne di string-> object che condivide (tramite metodi accessor) con sottoclassi. Diciamo che lo scrivo con HashMap s per cominciare, perché penso che sia la struttura appropriata da usare quando si scrive la class.

Più tardi, Mary scrive il codice sottoclassi. Ha qualcosa che deve fare con entrambe le things e moreThings things , così naturalmente lo mette in un metodo comune, e usa lo stesso tipo che ho usato su getThings / getMoreThings quando definiva il suo metodo:

 class SpecialFoo extends Foo { private void doSomething(HashMap t) { // ... } public void whatever() { this.doSomething(this.getThings()); this.doSomething(this.getMoreThings()); } // ...more... } 

Più tardi, decido che, in realtà, è meglio usare TreeMap invece di HashMap in Foo . Aggiorna Foo , cambiando HashMap in TreeMap . Ora, SpecialFoo non si compila più, perché ho rotto il contratto: Foo usava dire che forniva HashMap s, ma ora fornisce invece TreeMaps . Quindi dobbiamo riparare SpecialFoo ora (e questo genere di cose può propagarsi attraverso una base di codice).

A meno che non avessi una buona ragione per condividere che la mia implementazione stava usando una HashMap (e ciò accade), avrei dovuto dichiarare getThings e getMoreThings come solo restituendo Map senza essere più specifico di quello. In effetti, salvo una buona ragione per fare qualcos’altro, anche all’interno di Foo dovrei probabilmente dichiarare things e più things come Map , non come HashMap / TreeMap :

 class Foo { private Map things; // <== Changed private Map moreThings; // <== Changed protected Map getThings() { // <== Changed return this.things; } protected Map getMoreThings() { // <== Changed return this.moreThings; } public Foo() { this.things = new HashMap(); this.moreThings = new HashMap(); } // ...more... } 

Nota come sto usando Map ovunque posso, solo quando sono specifico quando creo gli oggetti reali.

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

 class SpecialFoo extends Foo { private void doSomething(Map t) { // <== Changed // ... } public void whatever() { this.doSomething(this.getThings()); this.doSomething(this.getMoreThings()); } } 

... e il cambiamento di Foo non avrebbe impedito a SpecialFoo compilare.

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

Questa non è una regola cieca, ma in generale, la codifica dell'interfaccia più generale sarà meno fragile della codifica per qualcosa di più specifico. Se l'avessi ricordato, non avrei creato un Foo che ha messo Mary in SpecialFoo con SpecialFoo . Se Mary si fosse ricordata di ciò, allora, anche se avessi incasinato Foo , lei avrebbe dichiarato il suo metodo privato con Map invece di HashMap e il mio contratto di HashMap Foo 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 una ragione per essere, incamminati verso l'interfaccia meno specifica.

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

Probabilmente avrà più senso leggere il tutorial dell’interfaccia di Sun

Stavo solo andando a 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 determinati metodi ad esso associati. ma ci sono modi diversi o la creazione di una mappa, come una HashMap, e questi diversi modi forniscono metodi unici che non tutte le mappe hanno.

Esattamente – e tu vuoi sempre usare l’interfaccia più generale che puoi. Considera ArrayList vs LinkedList. Enorme differenza nel modo in cui li si utilizza, ma se si utilizza “Elenco” è ansible passare tra loro prontamente.

In effetti, puoi sostituire il lato destro dell’inizializzatore con un’istruzione più dynamic. che ne dici di qualcosa del genere:

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

In questo modo, se si sta compilando la raccolta con un tipo di inserimento, si utilizzerà un elenco collegato (un ordinamento di inserimento in un elenco di array è criminale.) Ma se non è necessario mantenerlo ordinato e aggiungerlo, si utilizza un ArrayList (più efficiente per altre operazioni).

Questo è un tratto piuttosto grande perché le collezioni non sono l’esempio migliore, ma nel design OO uno dei concetti più importanti è l’utilizzo della facciata dell’interfaccia per accedere a oggetti diversi con lo stesso identico codice.

Modifica rispondendo al commento:

Per quanto riguarda il commento sulla mappa qui sotto, Sì utilizzando l’interfaccia “Mappa” ti limita solo a quei metodi, a meno che tu non restituisca la raccolta da Map a HashMap (che COMPLETAMENTE sconfigge lo scopo).

Spesso ciò che farai è creare un object e riempirlo usando il suo tipo specifico (HashMap), in una sorta di metodo “crea” o “inizializza”, ma quel metodo restituirà una “mappa” che non ha bisogno di essere manipolato come HashMap più.

Se mai dovessi lanciare comunque, probabilmente stai usando l’interfaccia sbagliata o il tuo codice non è strutturato abbastanza bene. Si noti che è accettabile che una sezione del proprio codice la tratti come una “HashMap” mentre l’altra lo tratta come una “Mappa”, ma questa dovrebbe scorrere “in basso”. in modo che tu non stia mai lanciando.

Notare anche l’aspetto semi-ordinato dei ruoli indicati dalle interfacce. Un LinkedList crea una buona pila o coda, un ArrayList crea una buona pila ma una coda orribile (di nuovo, una rimozione causerebbe uno spostamento dell’intero elenco) in modo che LinkedList implementa l’interfaccia Queue, ArrayList no.

inserisci la descrizione dell'immagine qui

Mappa con le seguenti implementazioni,

  1. HashMap Map m = new HashMap();

  2. LinkedHashMap Map m = new LinkedHashMap();

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

  4. WeakHashMap Map m = new WeakHashMap();

Supponiamo di aver creato un metodo (è solo un codice spudo).

 public void HashMap getMap(){ return map; } 

Supponiamo che i requisiti del progetto cambino ogni volta come segue,

  1. Il metodo dovrebbe restituire il contenuto della mappa – È necessario restituire HashMap .
  2. Il metodo deve restituire l’ordine di inserimento della chiave della mappa – È necessario modificare il tipo restituito da HashMap a LinkedHashMap .
  3. Il metodo deve restituire la chiave della mappa in ordine: è necessario modificare il tipo di LinkedHashMap in TreeMap .

Se il tuo metodo restituisce classi specifiche invece dell’interfaccia di Map , devi cambiare ogni volta il tipo di ritorno del metodo getMap() .

Tuttavia, se si utilizza la funzione di polimorfismo di java, anziché restituire la Map dell’interfaccia utilizzata per la class specifica, la riutilizzazione del codice è di origine e un impatto minore se cambiano i requisiti.

Come notato da TJ Crowder e Adamski, un riferimento è a un’interfaccia, l’altro a un’implementazione specifica dell’interfaccia. Secondo Joshua Block, dovresti sempre cercare di codificare le interfacce, per permetterti di gestire meglio le modifiche all’implementazione sottostante – ad esempio se HashMap non fosse improvvisamente l’ideale per la tua soluzione e avevi bisogno di cambiare l’implementazione della mappa, potresti comunque utilizzare la mappa interfaccia e modifica il tipo di istanza.

Nel tuo secondo esempio il riferimento “mappa” è di tipo Map , che è un’interfaccia implementata da HashMap (e altri tipi di Map ). Questa interfaccia è un contratto che dice che l’object mappa le chiavi dei valori e supporta varie operazioni (es. put , get ). Non dice nulla sull’implementazione della Map (in questo caso una HashMap ).

Il secondo approccio è generalmente preferito in quanto in genere non si desidera esporre l’implementazione della mappa specifica ai metodi che utilizzano la Map o una definizione API.

Map è il tipo statico di mappa, mentre HashMap è il tipo dinamico di mappa. Ciò significa che il compilatore tratterà l’object della mappa come uno di tipo Map, anche se in fase di esecuzione, potrebbe puntare a qualsiasi sottotipo di esso.

Questa pratica di programmazione contro interfacce invece di implementazioni ha il vantaggio di rimanere flessibile: è ansible ad esempio sostituire il tipo dinamico di mappa in fase di esecuzione, purché si tratti di un sottotipo di Map (ad esempio LinkedHashMap) e modificare il comportamento della mappa su il volo.

Una buona regola è quella di rimanere il più astratto ansible al livello dell’API: se per esempio un metodo che si sta programmando deve lavorare sulle mappe, allora è sufficiente dichiarare un parametro come Map invece del tipo HashMap più stretto (perché meno astratto) . In questo modo, il consumatore della tua API può essere flessibile sul tipo di implementazione della mappa che desidera passare al tuo metodo.

Crei le stesse mappe.

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

 public void foo (HashMap m1 = ...; Map m2 = ...; foo (m1); foo ((HashMap)m2); 

Aggiungendo la risposta votata in alto e molti altri sopra sottolineati come “più generico, migliore”, mi piacerebbe approfondire 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 aumentarla, come inserire, come mantenere l’indice univoco, ecc.

Diamo un’occhiata al codice sorgente:

In Map abbiamo il metodo di containsKey(Object key) :

 boolean containsKey(Object key); 

JavaDoc:

booleano java.util.Map.containsValue (valore dell’object)

Restituisce true 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 su un valore v tale che (value==null ? v==null : value.equals(v)) . Questa operazione richiederà probabilmente un tempo lineare nelle dimensioni della mappa per la maggior parte delle implementazioni dell’interfaccia Mappa.

Parametri: valore

valore la cui presenza in questa mappa è da scommettere

Returns: true

se questa mappa mappa una o più chiavi specificate

valueThrows:

ClassCastException – se il valore è di tipo inappropriato per questa mappa (opzionale)

NullPointerException – se il valore specificato è nullo e questa mappa non consente valori nulli (facoltativo)

Richiede le sue implementazioni per implementarlo, ma il “come” è alla sua libertà, solo per assicurarsi che ritorni corretto.

In HashMap :

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

Si scopre che HashMap utilizza hashcode per verificare se questa mappa contiene la chiave. Quindi ha il vantaggio dell’algoritmo di hash.

La mappa è l’interfaccia e Hashmap è la class che lo implementa.

Quindi in questa implementazione crei gli stessi oggetti

La mappa è l’interfaccia e Hashmap è una class che implementa l’interfaccia della mappa

HashMap è un’implementazione di Map quindi è quasi la stessa ma ha il metodo “clone ()” come vedo nella guida di riferimento))

 HashMap map1 = new HashMap(); Map map2 = new HashMap(); 

Prima di tutto Map è un’interfaccia con diverse implementazioni come HashMap , TreeHashMap , LinkedHashMap ecc. L’interfaccia funziona come una super class per la class di implementazione. Quindi, secondo la regola OOP, qualsiasi class concreta che implementa la Map è anche una Map . Ciò significa che possiamo assegnare / inserire qualsiasi variabile di tipo HashMap a una variabile di tipo Map senza alcun tipo di casting.

In questo caso possiamo assegnare map2 a map2 senza alcun cast o perdita di dati –

 map2 = map1