¿Cuál es la diferencia entre los objetos HashMap y Map en Java?

349

¿Cuál es la diferencia entre los siguientes mapas que creo (en otra pregunta, la gente respondió usándolos aparentemente de manera intercambiable y me pregunto si / cómo son diferentes):

HashMap<String, Object> map = new HashMap<String, Object>();
Map<String, Object> map = new HashMap<String, Object>();
Tony Stark
fuente
Supongamos que implementa usando HashMap y Mary usa Map. ¿Se compilará?
GilbertS

Respuestas:

446

No hay diferencia entre los objetos; Tienes un HashMap<String, Object>en ambos casos. Hay una diferencia en la interfaz que tiene con el objeto. En el primer caso, la interfaz es HashMap<String, Object>, mientras que en el segundo es Map<String, Object>. Pero el objeto subyacente es el mismo.

La ventaja de usar Map<String, Object>es que puede cambiar el objeto subyacente para que sea un tipo diferente de mapa sin romper su contrato con ningún código que lo esté usando. Si lo declara como HashMap<String, Object>, debe cambiar su contrato si desea cambiar la implementación subyacente.


Ejemplo: Digamos que escribo esta clase:

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 clase tiene un par de mapas internos de cadena-> objeto que comparte (a través de métodos de acceso) con subclases. Digamos que lo escribo con HashMaps para empezar porque creo que esa es la estructura apropiada para usar al escribir la clase.

Más tarde, Mary escribe código subclasificándolo. Ella tiene algo que necesita hacer con ambos thingsy moreThings, naturalmente, lo pone en un método común, y usa el mismo tipo que usé en getThings/ getMoreThingsal definir su método:

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

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

    // ...more...
}

Más tarde, decido que en realidad, es mejor si lo uso en TreeMaplugar de HashMapen Foo. Actualizo Foo, cambiando HashMapa TreeMap. Ahora, SpecialFooya no se compila, porque he roto el contrato: Foosolía decir que proporcionaba HashMaps, pero ahora está proporcionando en su TreeMapslugar. Así que tenemos que arreglarlo SpecialFooahora (y este tipo de cosas pueden pasar por una base de código).

A menos que tuviera una buena razón para compartir que mi implementación estaba usando un HashMap(y eso sucede), lo que debería haber hecho fue declarar getThingsy getMoreThingssimplemente regresar Map<String, Object>sin ser más específico que eso. De hecho, salvo una buena razón para hacer otra cosa, incluso dentro de Foolo que probablemente debería declarar thingsy moreThingscomo Map, no 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...
}

Tenga en cuenta que ahora estoy usando Map<String, Object>todo lo que puedo, solo siendo específico cuando creo los objetos reales.

Si hubiera hecho eso, entonces Mary habría hecho esto:

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

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

... y cambiar Foono habría hecho que SpecialFoodejara de compilar.

Las interfaces (y las clases base) nos permiten revelar todo lo que sea necesario , manteniendo nuestra flexibilidad bajo las cubiertas para realizar los cambios según corresponda. En general, queremos que nuestras referencias sean lo más básicas posible. Si no necesitamos saber que es un HashMap, simplemente llámelo a Map.

Esta no es una regla ciega, pero en general, la codificación de la interfaz más general será menos frágil que la codificación de algo más específico. Si lo hubiera recordado, no habría creado una Fooque hiciera fracasar a Mary SpecialFoo. Si Mary hubiera recordado eso, entonces, a pesar de que me equivoqué Foo, habría declarado su método privado en Maplugar de HashMapy el cambio de mi Foocontrato no habría afectado su código.

A veces no puedes hacer eso, a veces tienes que ser específico. Pero a menos que tenga una razón para estarlo, errar hacia la interfaz menos específica.

TJ Crowder
fuente
56

Map es una interfaz que implementa HashMap . La diferencia es que en la segunda implementación, su referencia a HashMap solo permitirá el uso de funciones definidas en la interfaz de Mapa, mientras que la primera permitirá el uso de cualquier función pública en HashMap (que incluye la interfaz de Mapa).

Probablemente tenga más sentido si lees el tutorial de interfaz de Sun

Noob de gráficos
fuente
Asumo: primero = HashMap <String, Object> map = new HashMap <String, Object> ();
OneWorld
Es similar a la frecuencia con que se implementa una Lista como ArrayList
Gerard
26

ingrese la descripción de la imagen aquí

Map tiene las siguientes implementaciones:

  1. HashMap Map m = new HashMap();

  2. LinkedHashMap Map m = new LinkedHashMap();

  3. Mapa de arboles Map m = new TreeMap();

  4. WeakHashMap Map m = new WeakHashMap();

Supongamos que ha creado un método (esto es solo pseudocódigo).

public void HashMap getMap(){
   return map;
}

Suponga que los requisitos de su proyecto cambian:

  1. El método debe devolver el contenido del mapa: es necesario devolverlo HashMap.
  2. El método debe devolver la clave del mapa con el fin de inserción - Necesidad de cambiar el tipo de retorno HashMapa LinkedHashMap.
  3. El método debe devolver las claves del mapa en orden ordenado: es necesario cambiar el tipo de retorno LinkedHashMapa TreeMap.

Si su método devuelve clases específicas en lugar de algo que implementa la Mapinterfaz, debe cambiar el tipo de getMap()método de retorno cada vez.

Pero si usa la función de polimorfismo de Java y, en lugar de devolver clases específicas, usa la interfaz Map, mejora la reutilización del código y reduce el impacto de los cambios de requisitos.

atish shimpi
fuente
17

Solo iba a hacer esto como un comentario sobre la respuesta aceptada, pero se volvió demasiado funky (odio no tener saltos de línea)

ah, entonces la diferencia es que, en general, Map tiene ciertos métodos asociados. pero hay diferentes formas de crear un mapa, como un HashMap, y estas diferentes formas proporcionan métodos únicos que no todos los mapas tienen.

Exactamente, y siempre desea utilizar la interfaz más general que pueda. Considere ArrayList vs LinkedList. Gran diferencia en cómo los usa, pero si usa "Lista" puede cambiar fácilmente entre ellos.

De hecho, puede reemplazar el lado derecho del inicializador con una declaración más dinámica. Qué tal algo como esto:

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

De esta manera, si va a completar la colección con un orden de inserción, usaría una lista vinculada (un orden de inserción en una lista de matriz es criminal). Pero si no necesita mantenerlo ordenado y solo está agregando, usa una ArrayList (más eficiente para otras operaciones).

Este es un tramo bastante grande aquí porque las colecciones no son el mejor ejemplo, pero en el diseño OO, uno de los conceptos más importantes es usar la fachada de la interfaz para acceder a diferentes objetos con exactamente el mismo código.

Editar respondiendo al comentario:

En cuanto a su comentario de mapa a continuación, Sí, usar la interfaz "Mapa" lo restringe solo a esos métodos, a menos que envíe la colección de Mapa a HashMap (que COMPLETAMENTE vence el propósito).

A menudo, lo que hará es crear un objeto y completarlo usando su tipo específico (HashMap), en algún tipo de método "crear" o "inicializar", pero ese método devolverá un "Mapa" que no necesita ser manipulado como un HashMap más.

Si alguna vez tiene que emitir por cierto, probablemente esté utilizando la interfaz incorrecta o su código no esté estructurado lo suficientemente bien. Tenga en cuenta que es aceptable que una sección de su código lo trate como un "HashMap" mientras que la otra lo trata como un "Mapa", pero esto debería fluir "hacia abajo". para que nunca lances

Observe también el aspecto semi-limpio de los roles indicados por las interfaces. Una LinkedList hace una buena pila o cola, una ArrayList hace una buena pila pero una cola horrible (una vez más, una eliminación provocaría un cambio en toda la lista), por lo que LinkedList implementa la interfaz de la cola, ArrayList no.

Bill K
fuente
pero en este ejemplo, solo obtengo los métodos de la clase Lista general, ¿verdad? independientemente de si lo hago un LinkedList () o un ArrayList ()? es solo que si uso el tipo de inserción (que imagino que debe ser un método para List que LinkedList y ArrayList obtienen por herencia) ¿funciona mucho más rápido en LinkedList?
Tony Stark
Supongo que lo que estoy buscando es si cuando digo Map <string, string> m = new HashMap <string, string> () mi Map m puede usar los métodos específicos de HashMap, o no. Estoy pensando que no puede?
Tony Stark
ah, espera, no, mi Mapa m desde arriba debe tener los métodos de HashMap.
Tony Stark
así que, básicamente, la única ventaja de usar Map en el 'sentido de la interfaz' es que si tengo un método que requiere un mapa, estoy garantizando que cualquier tipo de mapa funcionará en este método. pero si usé un hashmap, estoy diciendo que el método solo funciona con hashmaps. o, dicho de otra manera, mi método solo usa métodos definidos en la clase Map pero heredados por las otras clases que extienden Map.
Tony Stark
Además del beneficio que mencionó anteriormente, donde usar Lista significa que no necesito decidir qué tipo de Lista quiero hasta el tiempo de ejecución, mientras que si la cosa de la interfaz no existiera, tendría que elegir una antes de compilar y ejecutar
Tony Stark
12

Como señalaron TJ Crowder y Adamski, una referencia es a una interfaz, la otra a una implementación específica de la interfaz. Según Joshua Block, siempre debe intentar codificar las interfaces, para permitirle manejar mejor los cambios en la implementación subyacente, es decir, si HashMap de repente no era ideal para su solución y necesita cambiar la implementación del mapa, aún podría usar el Mapa interfaz y cambiar el tipo de instanciación.

aperkins
fuente
8

En su segundo ejemplo, la referencia de "mapa" es de tipo Map, que es una interfaz implementada por HashMap(y otros tipos de Map). Esta interfaz es un contrato que dice que el objeto asigna claves a valores y admite varias operaciones (por ejemplo put, get). No dice nada acerca de la implementación de Map(en este caso a HashMap).

Por lo general, se prefiere el segundo enfoque, ya que normalmente no querría exponer la implementación del mapa específico a los métodos que utilizan Mapo mediante una definición de API.

Adamski
fuente
8

Mapa es el tipo de mapa estático , mientras que HashMap es el tipo dinámico de mapa. Esto significa que el compilador tratará su objeto de mapa como uno del tipo Mapa, aunque en tiempo de ejecución, puede apuntar a cualquier subtipo de él.

Esta práctica de programar contra interfaces en lugar de implementaciones tiene el beneficio adicional de permanecer flexible: por ejemplo, puede reemplazar el tipo dinámico de mapa en tiempo de ejecución, siempre que sea un subtipo de Mapa (por ejemplo, LinkedHashMap) y cambiar el comportamiento del mapa en la mosca.

Una buena regla general es permanecer lo más abstracto posible en el nivel API: si, por ejemplo, un método que está programando debe funcionar en mapas, entonces es suficiente declarar un parámetro como Mapa en lugar del tipo HashMap más estricto (porque menos abstracto) . De esa forma, el consumidor de su API puede ser flexible sobre qué tipo de implementación de Mapa quiere pasar a su método.

Matías
fuente
4

Agregando a la respuesta más votada y muchas más arriba destacando el "más genérico, mejor", me gustaría cavar un poco más.

Mapes el contrato de estructura, mientras que HashMapes una implementación que proporciona sus propios métodos para tratar diferentes problemas reales: cómo calcular el índice, cuál es la capacidad y cómo incrementarlo, cómo insertarlo, cómo mantener el índice único, etc.

Veamos el código fuente:

En Maptenemos el método de containsKey(Object key):

boolean containsKey(Object key);

JavaDoc:

boolean java.util.Map.containsValue (valor del objeto)

Devuelve verdadero si este mapa asigna una o más claves al valor especificado. Más formalmente, devuelve verdadero si y solo si este mapa contiene al menos una asignación a un valor vtal que (value==null ? v==null : value.equals(v)). Esta operación probablemente requerirá un tiempo lineal en el tamaño del mapa para la mayoría de las implementaciones de la interfaz del mapa.

Parámetros: valor

valor cuya presencia en este mapa se va a probar

Devoluciones: verdadero

si este mapa asigna una o más claves a la especificada

valueThrows:

ClassCastException: si el valor es de un tipo inapropiado para este mapa (opcional)

NullPointerException: si el valor especificado es nulo y este mapa no permite valores nulos (opcional)

Requiere sus implementaciones para implementarlo, pero el "cómo" está en libertad, solo para garantizar que regrese correctamente.

En HashMap:

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

Resulta que HashMapusa el código hash para probar si este mapa contiene la clave. Por lo tanto, tiene el beneficio del algoritmo hash.

WesternGun
fuente
3

Creas los mismos mapas.

Pero puedes llenar la diferencia cuando la uses. Con el primer caso, podrá usar métodos especiales de HashMap (pero no recuerdo a nadie realmente útil), y podrá pasarlo como un parámetro de HashMap:

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

...

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

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

Map es interfaz y Hashmap es una clase que implementa Map Interface


fuente
1

Map es la interfaz y Hashmap es la clase que implementa eso.

Entonces en esta implementación creas los mismos objetos

Diego Dias
fuente
0

HashMap es una implementación de Map, por lo que es bastante similar pero tiene el método "clone ()" como veo en la guía de referencia))

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

En primer lugar Mapes una interfaz que tiene una implementación diferente como - HashMap, TreeHashMap, LinkedHashMapetc interfaz funciona como una superclase de la clase que implementa. Entonces, de acuerdo con la regla de OOP, cualquier clase concreta que implemente también Mapes una Map. Eso significa que podemos asignar / poner cualquier HashMapvariable de tipo a una Mapvariable de tipo sin ningún tipo de conversión.

En este caso se puede asignar map1a map2sin ningún tipo de fundición o cualquier perdida de datos -

map2 = map1
Razib
fuente