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 ()