Qual é a diferença entre os objetos HashMap e Map em Java?

349

Qual é a diferença entre os seguintes mapas que eu crio (em outra pergunta, as pessoas responderam usando-os aparentemente de forma intercambiável e eu estou pensando se / como eles são diferentes):

HashMap<String, Object> map = new HashMap<String, Object>();
Map<String, Object> map = new HashMap<String, Object>();
Tony Stark
fonte
Suponha que você implemente usando HashMap e Mary use Map. Será que vai compilar?
GilbertS

Respostas:

446

Não há diferença entre os objetos; você tem um HashMap<String, Object>nos dois casos. Há uma diferença na interface que você tem para o objeto. No primeiro caso, a interface é HashMap<String, Object>, enquanto no segundo é Map<String, Object>. Mas o objeto subjacente é o mesmo.

A vantagem de usar Map<String, Object>é que você pode alterar o objeto subjacente para ser um tipo diferente de mapa sem quebrar seu contrato com qualquer código que o esteja usando. Se você o declarar como HashMap<String, Object>, precisará alterar seu contrato se desejar alterar a implementação subjacente.


Exemplo: Digamos que eu escreva esta 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...
}

A classe possui alguns mapas internos de string-> objeto que ele compartilha (por meio de métodos acessadores) com subclasses. Digamos que eu escreva com HashMaps para começar, porque acho que essa é a estrutura apropriada para usar ao escrever a classe.

Mais tarde, Maria escreve código subclassificando-o. Ela tem algo que precisa fazer com ambos thingse moreThings, portanto, naturalmente, coloca isso em um método comum, e ela usa o mesmo tipo que eu usei getThings/ getMoreThingsao definir seu 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...
}

Mais tarde, eu decido que na verdade, é melhor se eu usar TreeMapem vez de HashMapno Foo. Eu atualizo Foo, mudando HashMappara TreeMap. Agora, SpecialFoonão compila mais, porque eu quebrei o contrato: Foocostumava dizer que fornecia HashMaps, mas agora está fornecendo TreeMaps. Portanto, temos que corrigir SpecialFooagora (e esse tipo de coisa pode se espalhar por uma base de código).

A menos que eu tenha realmente um bom motivo para compartilhar que minha implementação estava usando um HashMap(e isso acontece), o que eu deveria ter feito era declarar getThingse getMoreThingsapenas retornar Map<String, Object>sem ser mais específico do que isso. De fato, exceto por uma boa razão para fazer outra coisa, mesmo dentro de Fooeu provavelmente deveria declarar thingse moreThingscomo Map, não 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...
}

Observe como agora estou usando em Map<String, Object>todos os lugares que posso, apenas sendo específico quando crio os objetos reais.

Se eu tivesse feito isso, Mary teria feito isso:

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

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

... e mudar Foonão teria feito SpecialFooparar de compilar.

As interfaces (e classes de base) nos permitem revelar apenas o necessário , mantendo nossa flexibilidade oculta para fazer as alterações necessárias. Em geral, queremos que nossas referências sejam o mais básicas possível. Se não precisamos saber que é um HashMap, basta chamá-lo de Map.

Essa não é uma regra cega, mas, em geral, a codificação para a interface mais geral será menos frágil do que a codificação para algo mais específico. Se eu tivesse me lembrado disso, não teria criado uma Fooque deixasse Mary com defeito SpecialFoo. Se Mary tivesse se lembrado disso, mesmo que eu errei Foo, ela teria declarado seu método privado em Mapvez de HashMape meu Foocontrato de mudança não teria afetado seu código.

Às vezes você não pode fazer isso, às vezes você precisa ser específico. Mas, a menos que você tenha um motivo para estar, erre na interface menos específica.

TJ Crowder
fonte
56

Map é uma interface que o HashMap implementa. A diferença é que, na segunda implementação, sua referência ao HashMap permitirá apenas o uso das funções definidas na interface do Mapa, enquanto a primeira permitirá o uso de quaisquer funções públicas no HashMap (que inclui a interface do Mapa).

Provavelmente fará mais sentido se você ler o tutorial de interface da Sun

Graphics Noob
fonte
Eu assumo: primeiro = HashMap <String, Object> map = new HashMap <String, Object> ();
OneWorld
É semelhante à frequência com que uma Lista é implementada como ArrayList
Gerard
26

insira a descrição da imagem aqui

O mapa tem as seguintes implementações:

  1. HashMap Map m = new HashMap();

  2. LinkedHashMap Map m = new LinkedHashMap();

  3. Mapa da Árvore Map m = new TreeMap();

  4. WeakHashMap Map m = new WeakHashMap();

Suponha que você tenha criado um método (este é apenas pseudocódigo).

public void HashMap getMap(){
   return map;
}

Suponha que os requisitos do seu projeto sejam alterados:

  1. O método deve retornar o conteúdo do mapa - Precisa retornar HashMap.
  2. O método deve retornar as chaves do mapa em ordem de inserção - Precisa alterar o tipo de retorno HashMappara LinkedHashMap.
  3. O método deve retornar as chaves do mapa na ordem classificada - Precisa alterar o tipo de retorno LinkedHashMappara TreeMap.

Se o seu método retornar classes específicas em vez de algo que implemente a Mapinterface, você deverá alterar o tipo de retorno do getMap()método a cada vez.

Mas se você usar o recurso de polimorfismo do Java e, em vez de retornar classes específicas, usar a interface Map, ele melhora a reutilização do código e reduz o impacto das alterações de requisitos.

atish shimpi
fonte
17

Eu ia fazer isso como um comentário sobre a resposta aceita, mas ela ficou muito divertida (eu odeio não ter quebras de linha)

ah, então a diferença é que, em geral, o Map tem certos métodos associados a ele. mas existem maneiras diferentes ou a criação de um mapa, como um HashMap, e essas maneiras diferentes fornecem métodos exclusivos que nem todos os mapas possuem.

Exatamente - e você sempre deseja usar a interface mais geral possível. Considere ArrayList vs LinkedList. Diferença enorme na maneira como você os utiliza, mas se você usar a "Lista", poderá alternar entre eles facilmente.

De fato, você pode substituir o lado direito do inicializador por uma instrução mais dinâmica. Que tal algo como isso:

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

Dessa forma, se você for preencher a coleção com uma classificação de inserção, use uma lista vinculada (uma classificação de inserção em uma lista de matriz é criminosa). Mas se você não precisar mantê-la classificada e apenas anexar, você usa um ArrayList (mais eficiente para outras operações).

Essa é uma extensão bastante grande aqui, porque coleções não são o melhor exemplo, mas no design de OO, um dos conceitos mais importantes é usar a fachada da interface para acessar diferentes objetos com o mesmo código.

Edite respondendo ao comentário:

Quanto ao comentário do seu mapa abaixo, Sim, usando a interface "Mapa" restringe você apenas a esses métodos, a menos que você converta a coleção de Map novamente para o HashMap (que derruba COMPLETAMENTE o objetivo).

Geralmente, o que você faz é criar um objeto e preenchê-lo usando seu tipo específico (HashMap), em algum tipo de método "create" ou "initialize", mas esse método retornará um "Map" que não precisa ser manipulado como um HashMap mais.

Se você tiver que transmitir pelo caminho, provavelmente está usando a interface errada ou seu código não está estruturado o suficiente. Observe que é aceitável que uma seção do seu código a trate como um "HashMap", enquanto a outra a trate como um "Mapa", mas isso deve fluir "para baixo". para que você nunca esteja lançando.

Observe também o aspecto semi-organizado das funções indicadas pelas interfaces. Um LinkedList cria uma boa pilha ou fila, um ArrayList cria uma boa pilha, mas uma fila horrível (novamente, uma remoção causaria uma mudança na lista inteira), portanto o LinkedList implementa a interface da Fila, o ArrayList não.

Bill K
fonte
mas neste exemplo, eu só recebo os métodos da classe List geral, certo? independentemente de eu torná-lo um LinkedList () ou um ArrayList ()? é que, se eu usar a classificação por inserção (que imagino ser um método para List que LinkedList e ArrayList recebem por herança), ele funcionará muito mais rápido no LinkedList?
Tony Stark
Eu acho que o que estou procurando é se, quando digo Map <string, string> m = new HashMap <string, string> (), meu Map m pode usar os métodos específicos para o HashMap ou não. Eu estou pensando que não pode?
Tony Stark
ah, espere, não, meu mapa m de cima deve ter os métodos do HashMap.
Tony Stark
Então, basicamente, a única vantagem de usar o Mapa no "sentido da interface" é que, se eu tiver um método que exija um mapa, garanto que qualquer tipo de mapa funcionará nesse método. mas se eu usei um hashmap, estou dizendo que o método só funciona com hashmaps. ou, dito de outra maneira, meu método usa apenas métodos definidos na classe Map, mas herdados pelas outras Classes que estendem o Map.
Tony Stark
além da vantagem mencionada acima, onde usar a lista significa que não preciso decidir qual tipo de lista eu quero até o tempo de execução, enquanto que, se a interface não existisse, eu teria que escolher uma antes de compilar e executar
Tony Stark
12

Conforme observado por TJ Crowder e Adamski, uma referência é a uma interface, a outra a uma implementação específica da interface. De acordo com Joshua Block, você deve sempre tentar codificar para interfaces, para permitir lidar melhor com as alterações na implementação subjacente - por exemplo, se o HashMap de repente não era ideal para a sua solução e você precisava alterar a implementação do mapa, você ainda poderia usar o Mapa interface e altere o tipo de instanciação.

aperkins
fonte
8

No seu segundo exemplo, a referência "map" é do tipo Map, que é uma interface implementada por HashMap(e outros tipos de Map). Essa interface é um contrato dizendo que o objeto mapeia chaves para valores e suporta várias operações (por exemplo put, get). Não diz nada sobre a implementação do Map(neste caso, a HashMap).

A segunda abordagem é geralmente preferida, pois você normalmente não deseja expor a implementação específica do mapa a métodos que usam a Mapou por meio de uma definição de API.

Adamski
fonte
8

Mapa é o tipo estático de mapa, enquanto HashMap é o tipo dinâmico de mapa. Isso significa que o compilador tratará seu objeto de mapa como sendo do tipo Map, mesmo que em tempo de execução, ele possa apontar para qualquer subtipo dele.

Essa prática de programação em interfaces em vez de implementações tem o benefício adicional de permanecer flexível: por exemplo, você pode substituir o tipo dinâmico de mapa em tempo de execução, desde que seja um subtipo de Map (por exemplo, LinkedHashMap) e alterar o comportamento do mapa em o voo.

Uma boa regra geral é permanecer o mais abstrato possível no nível da API: se, por exemplo, um método que você está programando deve funcionar em mapas, basta declarar um parâmetro como Map em vez do tipo HashMap mais rígido (porque menos abstrato) . Dessa forma, o consumidor da sua API pode ser flexível sobre o tipo de implementação do Mapa que deseja transmitir ao seu método.

Matthias
fonte
4

Somando a resposta votada ao topo e muitas respostas acima, enfatizando o "mais genérico, melhor", gostaria de cavar um pouco mais.

Mapé o contrato de estrutura e HashMapuma implementação que fornece métodos próprios para lidar com diferentes problemas reais: como calcular o índice, qual é a capacidade e como incrementá-lo, como inserir, como manter o índice exclusivo etc.

Vamos dar uma olhada no código fonte:

Em Mapnós temos o método de containsKey(Object key):

boolean containsKey(Object key);

JavaDoc:

booleano java.util.Map.containsValue (valor do objeto)

Retorna true se este mapa mapeia uma ou mais chaves para o valor especificado. Mais formalmente, retorna true se e somente se esse mapa contiver pelo menos um mapeamento para um valor vcomo esse (value==null ? v==null : value.equals(v)). Essa operação provavelmente exigirá tempo linear no tamanho do mapa para a maioria das implementações da interface do Mapa.

Parâmetros: value

valor cuja presença neste mapa é apostada

Retorna: true

se este mapa mapeia uma ou mais chaves para o especificado

valueThrows:

ClassCastException - se o valor for de um tipo inadequado para este mapa (opcional)

NullPointerException - se o valor especificado for nulo e este mapa não permitir valores nulos (opcional)

Ele exige suas implementações para implementá-lo, mas o "como fazer" está livre, apenas para garantir que ele retorne correto.

Em HashMap:

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

Acontece que HashMapusa código hash para testar se este mapa contém a chave. Portanto, ele tem o benefício do algoritmo de hash.

WesternGun
fonte
3

Você cria os mesmos mapas.

Mas você pode preencher a diferença quando a usar. No primeiro caso, você poderá usar métodos HashMap especiais (mas não me lembro de ninguém realmente útil), e poderá passá-lo como um parâmetro HashMap:

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

...

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

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

Map é interface e Hashmap é uma classe que implementa Map Interface


fonte
1

Mapa é a interface e Hashmap é a classe que implementa isso.

Portanto, nesta implementação, você cria os mesmos objetos

Diego Dias
fonte
0

O HashMap é uma implementação do Map, portanto é o mesmo, mas possui o método "clone ()", como eu vejo no guia de referência))

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

Primeiro de tudo Mapé uma interface que tem implementação diferente como - HashMap, TreeHashMap, LinkedHashMapetc. interface funciona como uma super classe para a classe de execução. Portanto, de acordo com a regra da OOP, qualquer classe concreta que implementa também Mapé uma Map. Isso significa que podemos atribuir / colocar qualquer HashMapvariável de tipo a uma Mapvariável de tipo sem nenhum tipo de conversão.

Nesse caso, podemos atribuir map1a ele map2sem vazamento ou perda de dados -

map2 = map1
Razib
fonte