Um guia para Java HashMap

Um guia para Java HashMap

1. Visão geral

*Neste artigo, veremos como usar _HashMap_ em Java e veremos como ele funciona internamente.*

Uma classe muito semelhante a HashMap é Hashtable. Consulte alguns de nossos outros artigos para saber mais sobre a própria https://www..com/java-hash-table [classe java.util.Hashtable] e o https://www..com/hashmap- hashtable-diferenças [diferenças entre HashMap e Hashtable].

2. Uso básico

*Vamos primeiro ver o que significa que _HashMap_ é um mapa. Um mapa é um mapeamento de valor-chave, o que significa que todas as chaves são mapeadas para exatamente um valor e que podemos usar a chave para recuperar o valor correspondente de um mapa.*

Alguém pode perguntar por que não simplesmente adicionar o valor a uma lista. Por que precisamos de um HashMap? O motivo simples é o desempenho. Se quisermos encontrar um elemento específico em uma lista, a complexidade do tempo é _O (n) _ e, se a lista estiver classificada, será _O (log n) _ usando, por exemplo, uma pesquisa binária.

A vantagem de um HashMap é que a complexidade de tempo para inserir e recuperar um valor é O (1) _ em média. Veremos como isso pode ser alcançado mais tarde. Vamos primeiro ver como usar o _HashMap.

2.1. Configuração

Vamos criar uma classe simples que usaremos ao longo do artigo:

public class Product {

    private String name;
    private String description;
    private List<String> tags;

   //standard getters/setters/constructors

    public Product addTagsOfOtherProdcut(Product product) {
        this.tags.addAll(product.getTags());
        return this;
    }
}

2.2. Put

Agora podemos criar um HashMap com a chave do tipo String e elementos do tipo Product:

Map<String, Product> productsByName = new HashMap<>();

E adicione produtos ao nosso HashMap:

Product eBike = new Product("E-Bike", "A bike with a battery");
Product roadBike = new Product("Road bike", "A bike for competition");
productsByName.put(eBike.getName(), eBike);
productsByName.put(roadBike.getName(), roadBike);

2.3. Get

Podemos recuperar um valor do mapa por sua chave:

Product nextPurchase = productsByName.get("E-Bike");
assertEquals("A bike with a battery", nextPurchase.getDescription());

Se tentarmos encontrar um valor para uma chave que não existe no mapa, obteremos um valor null:

Product nextPurchase = productsByName.get("Car");
assertNull(nextPurchase);

E se inserirmos um segundo valor com a mesma chave, obteremos apenas o último valor inserido para essa chave:

Product newEBike = new Product("E-Bike", "A bike with a better battery");
productsByName.put(newEBike.getName(), newEBike);
assertEquals("A bike with a better battery", productsByName.get("E-Bike"));

2.4. Nulo como a chave

HashMap também nos permite ter null como chave:

Product defaultProduct = new Product("Chocolate", "At least buy chocolate");
productsByName.put(null, defaultProduct);

Product nextPurchase = productsByName.get(null);
assertEquals("At least buy chocolate", nextPurchase.getDescription());

2.5. Valores com a mesma chave

Além disso, podemos inserir o mesmo objeto duas vezes com uma chave diferente:

productsByName.put(defaultProduct.getName(), defaultProduct);
assertSame(productsByName.get(null), productsByName.get("Chocolate"));

2.6. Remover um valor

Podemos remover um mapeamento de valor-chave do HashMap:

productsByName.remove("E-Bike");
assertNull(productsByName.get("E-Bike"));

2.7. Verifique se existe uma chave ou um valor no mapa

Para verificar se uma chave está presente no mapa, podemos usar o método _containsKey () _:

productsByName.containsKey("E-Bike");

Ou, para verificar se um valor está presente no mapa, podemos usar o método _containsValue () _:

productsByName.containsValue(eBike);

As duas chamadas de método retornarão true em nosso exemplo. Embora pareçam muito semelhantes, há uma diferença importante no desempenho entre essas duas chamadas de método. A complexidade para verificar se existe uma chave é _O (1) _, enquanto a complexidade para verificar um elemento é _O (n), _, pois é necessário fazer um loop sobre todos os elementos no mapa.

2.8. Iterando sobre um HashMap

Existem três maneiras básicas de iterar sobre todos os pares de valores-chave em um HashMap.

Podemos iterar sobre o conjunto de todas as chaves:

for(String key : productsByName.keySet()) {
    Product product = productsByName.get(key);
}

Ou podemos iterar sobre o conjunto de todas as entradas:

for(Map.Entry<String, Product> entry : productsByName.entrySet()) {
    Product product =  entry.getValue();
    String key = entry.getKey();
   //do something with the key and value
}

Por fim, podemos iterar sobre todos os valores:

List<Product> products = new ArrayList<>(productsByName.values());

3. A chave

*Podemos usar qualquer classe como a chave em nosso _HashMap_. No entanto, para que o mapa funcione corretamente, precisamos fornecer uma implementação para _equals () _ e* __ *hashCode ().* __ Digamos que queremos ter um mapa com o produto como chave e o preço como valor:
HashMap<Product, Integer> priceByProduct = new HashMap<>();
priceByProduct.put(eBike, 900);

Vamos implementar os métodos _equals () _ e _hashCode () _:

@Override
public boolean equals(Object o) {
    if (this == o) {
        return true;
    }
    if (o == null || getClass() != o.getClass()) {
        return false;
    }

    Product product = (Product) o;
    return Objects.equals(name, product.name) &&
      Objects.equals(description, product.description);
}

@Override
public int hashCode() {
    return Objects.hash(name, description);
}
*Observe que _hashCode () _ e _equals () _ precisam ser substituídos apenas para classes que queremos usar como chaves de mapa, não para classes que são usadas apenas como valores em um mapa.* Veremos por que isso é necessário na seção 5 deste artigo.

4. Métodos adicionais a partir do Java 8

O Java 8 adicionou vários métodos de estilo funcional ao HashMap. Nesta seção, veremos alguns desses métodos.

*Para cada método, veremos dois exemplos.* O primeiro exemplo mostra como usar o novo método e o segundo exemplo mostra como obter o mesmo em versões anteriores do Java.

Como esses métodos são bastante diretos, não veremos exemplos mais detalhados.

4.1. para cada()

O método forEach é a maneira no estilo funcional de iterar sobre todos os elementos no mapa:

productsByName.forEach( (key, product) -> {
    System.out.println("Key: " + key + " Product:" + product.getDescription());
   //do something with the key and value
});

Antes do Java 8:

for(Map.Entry<String, Product> entry : productsByName.entrySet()) {
    Product product =  entry.getValue();
    String key = entry.getKey();
   //do something with the key and value
}

Nosso artigo https://www..com/foreach-java [Guia para o Java 8 forEach] aborda o loop forEach em mais detalhes.

4.2. _getOrDefault () _

Usando o método _getOrDefault () _, podemos obter um valor do mapa ou retornar um elemento padrão, caso não haja mapeamento para a chave fornecida:

Product chocolate = new Product("chocolate", "something sweet");
Product defaultProduct = productsByName.getOrDefault("horse carriage", chocolate);
Product bike = productsByName.getOrDefault("E-Bike", chocolate);

Antes do Java 8:

Product bike2 = productsByName.containsKey("E-Bike")
    ? productsByName.get("E-Bike")
    : chocolate;
Product defaultProduct2 = productsByName.containsKey("horse carriage")
    ? productsByName.get("horse carriage")
    : chocolate;

4.3. _putIfAbsent () _

Com esse método, podemos adicionar um novo mapeamento, mas apenas se ainda não houver um mapeamento para a chave fornecida:

productsByName.putIfAbsent("E-Bike", chocolate);

Antes do Java 8:

if(productsByName.containsKey("E-Bike")) {
    productsByName.put("E-Bike", chocolate);
}

Nosso artigo https://www..com/java-merge-maps [Mesclando dois mapas com Java 8] analisa mais de perto esse método.

4.4. _merge () _

E com _https://www..com/java-merge-maps [merge ()], _ podemos modificar o valor de uma determinada chave se existir um mapeamento ou adicionar um novo valor caso contrário:

Product eBike2 = new Product("E-Bike", "A bike with a battery");
eBike2.getTags().add("sport");
productsByName.merge("E-Bike", eBike2, Product::addTagsOfOtherProdcut);

Antes do Java 8:

if(productsByName.containsKey("E-Bike")) {
    productsByName.get("E-Bike").addTagsOfOtherProdcut(eBike2);
} else {
    productsByName.put("E-Bike", eBike2);
}

4.5. calcular()

Com o método _compute () _, podemos calcular o valor para uma determinada chave:

productsByName.compute("E-Bike", (k,v) -> {
    if(v != null) {
        return v.addTagsOfOtherProdcut(eBike2);
    } else {
        return eBike2;
    }
});

Antes do Java 8:

if(productsByName.containsKey("E-Bike")) {
    productsByName.get("E-Bike").addTagsOfOtherProdcut(eBike2);
} else {
    productsByName.put("E-Bike", eBike2);
}

Vale a pena notar que os métodos merge () _ e _compute () _ são bastante semelhantes. O método _compute ()