HashMap do Java é uma das classes de coleção mais populares em Java. HashMap do Java é uma implementação baseada em tabela de hash. HashMap em Java estende a classe AbstractMap que implementa a interface Mapa.
HashMap do Java
Alguns pontos importantes sobre o HashMap em Java são;
- O HashMap do Java permite chave nula e valores nulos.
- HashMap não é uma coleção ordenada. Você pode iterar sobre as entradas do HashMap através do conjunto de chaves, mas elas não são garantidas de estarem na ordem em que foram adicionadas ao HashMap.
- HashMap é quase igual ao Hashtable, exceto que ele não é sincronizado e permite chave e valores nulos.
- HashMap usa sua classe interna Node<K, V> para armazenar as entradas do mapa.
- HashMap armazena as entradas em várias listas encadeadas simples, chamadas de compartimentos ou bins. O número padrão de compartimentos é 16 e sempre é uma potência de 2.
- HashMap usa os métodos hashCode() e equals() nas chaves para operações de get e put. Portanto, o objeto chave do HashMap deve fornecer uma boa implementação desses métodos. Esse é o motivo pelo qual classes imutáveis são mais adequadas para chaves, por exemplo, String e Integer.
- HashMap do Java não é thread-safe, para ambiente multithreaded você deve usar a classe ConcurrentHashMap ou obter um mapa sincronizado usando o método
Collections.synchronizedMap()
.
Construtores do Java HashMap
O Java HashMap fornece quatro construtores.
- public HashMap(): Construtor HashMap mais comumente utilizado. Este construtor criará um HashMap vazio com capacidade inicial padrão de 16 e fator de carga de 0,75
- public HashMap(int capacidadeInicial): Este construtor HashMap é usado para especificar a capacidade inicial e o fator de carga de 0,75. Isso é útil para evitar rehashing se você souber o número de mapeamentos a serem armazenados no HashMap.
- public HashMap(int capacidadeInicial, float fatorDeCarga): Este construtor HashMap criará um HashMap vazio com a capacidade e o fator de carga especificados. Você pode usar isso se souber o número máximo de mapeamentos a serem armazenados no HashMap. Em cenários comuns, você deve evitar isso porque o fator de carga 0,75 oferece uma boa compensação entre espaço e custo de tempo.
- public HashMap(Map<? extends K, ? extends V> m): Cria um Mapa com os mesmos mapeamentos do mapa especificado e com fator de carga de 0,75
Exemplo de Construtores do Java HashMap
O trecho de código abaixo mostra um exemplo de HashMap usando todos os construtores mencionados acima.
Map<String, String> map1 = new HashMap<>();
Map<String, String> map2 = new HashMap<>(2^5);
Map<String, String> map3 = new HashMap<>(32,0.80f);
Map<String,String> map4 = new HashMap<>(map1);
Métodos do HashMap em Java
Vamos dar uma olhada nos métodos importantes do HashMap em Java.
- public void clear(): Este método do HashMap removerá todos os mapeamentos e o HashMap se tornará vazio.
- public boolean containsKey(Object key): Este método retorna ‘true’ se a chave existir, caso contrário, retornará ‘false’.
- public boolean containsValue(Object value): Este método do HashMap retorna true se o valor existir, caso contrário, false.
- public Set<Map.Entry<K,V>> entrySet(): Este método retorna uma visualização de conjunto dos mapeamentos do HashMap. Este conjunto é suportado pelo mapa, então as alterações no mapa são refletidas no conjunto, e vice-versa.
- public V get(Object key): Retorna o valor mapeado para a chave especificada, ou null se não houver mapeamento para a chave.
- public boolean isEmpty(): Um método de utilidade que retorna true se não houver mapeamentos de chave-valor presentes.
- public Set<K> keySet(): Retorna uma visualização de conjunto das chaves contidas neste mapa. O conjunto é suportado pelo mapa, então as alterações no mapa são refletidas no conjunto, e vice-versa.
- public V put(K key, V value): Associa o valor especificado à chave especificada neste mapa. Se o mapa anteriormente continha um mapeamento para a chave, o valor antigo é substituído.
- public void putAll(Map<? extends K, ? extends V> m): Copia todas as associações do mapa especificado para este mapa. Essas associações substituirão quaisquer associações que este mapa tinha para quaisquer das chaves atualmente no mapa especificado.
- public V remove(Object key): Remove a associação para a chave especificada deste mapa, se presente.
- public int size(): Retorna o número de associações de chave-valor neste mapa.
- public Collection<V> values(): Retorna uma visualização da coleção dos valores contidos neste mapa. A coleção é suportada pelo mapa, então as alterações no mapa são refletidas na coleção, e vice-versa.
Há muitos novos métodos em HashMap introduzidos no Java 8.
- public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction): Se a chave especificada ainda não estiver associada a um valor (ou estiver mapeada como nula), este método tenta calcular seu valor usando a função de mapeamento fornecida e o insere no HashMap a menos que seja Nulo.
- public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction): Se o valor para a chave especificada estiver presente e não for nulo, tenta calcular uma nova associação dada a chave e seu valor atual mapeado.
- public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction): Este método do HashMap tenta calcular uma associação para a chave especificada e seu valor atual mapeado.
- public void forEach(BiConsumer<? super K, ? super V> action): Este método executa a ação fornecida para cada entrada neste mapa.
- public V getOrDefault(Object key, V defaultValue): Mesmo que get exceto que defaultValue é retornado se nenhuma correspondência for encontrada para a chave especificada.
- public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction): Se a chave especificada ainda não estiver associada a um valor ou estiver associada a null, associa-a ao valor não nulo dado. Caso contrário, substitui o valor associado pelos resultados da função de remapeamento fornecida, ou remove se o resultado for null.
- public V putIfAbsent(K key, V value): Se a chave especificada ainda não estiver associada a um valor (ou estiver mapeada para null), associa-a ao valor dado e retorna null, caso contrário, retorna o valor atual.
- public boolean remove(Object key, Object value): Remove a entrada para a chave especificada apenas se ela estiver atualmente mapeada para o valor especificado.
- public boolean replace(K key, V oldValue, V newValue): Substitui a entrada para a chave especificada apenas se atualmente estiver mapeada para o valor especificado.
- public V replace(K key, V value): Substitui a entrada para a chave especificada apenas se ela estiver atualmente mapeada para algum valor.
- public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function): Substitui o valor de cada entrada pelo resultado da invocação da função fornecida para essa entrada.
Exemplo de Java HashMap
Aqui está um programa Java simples para os métodos comumente utilizados em HashMap.
package com.journaldev.examples;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class HashMapExample {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("1", "1"); // put example
map.put("2", "2");
map.put("3", "3");
map.put("4", null); // null value
map.put(null, "100"); // null key
String value = map.get("3"); // get example
System.out.println("Key = 3, Value = " + value);
value = map.getOrDefault("5", "Default Value");
System.out.println("Key = 5, Value=" + value);
boolean keyExists = map.containsKey(null);
boolean valueExists = map.containsValue("100");
System.out.println("keyExists=" + keyExists + ", valueExists=" + valueExists);
Set<Entry<String, String>> entrySet = map.entrySet();
System.out.println(entrySet);
System.out.println("map size=" + map.size());
Map<String, String> map1 = new HashMap<>();
map1.putAll(map);
System.out.println("map1 mappings= " + map1);
String nullKeyValue = map1.remove(null);
System.out.println("map1 null key value = " + nullKeyValue);
System.out.println("map1 after removing null key = " + map1);
Set<String> keySet = map.keySet();
System.out.println("map keys = " + keySet);
Collection<String> values = map.values();
System.out.println("map values = " + values);
map.clear();
System.out.println("map is empty=" + map.isEmpty());
}
}
Abaixo está a saída do exemplo de programa Java HashMap acima.
Key = 3, Value = 3
Key = 5, Value=Default Value
keyExists=true, valueExists=true
[null=100, 1=1, 2=2, 3=3, 4=null]
map size=5
map1 mappings= {null=100, 1=1, 2=2, 3=3, 4=null}
map1 null key value = 100
map1 after removing null key = {1=1, 2=2, 3=3, 4=null}
map keys = [null, 1, 2, 3, 4]
map values = [100, 1, 2, 3, null]
map is empty=true
Como o HashMap funciona em Java?
HashMap em Java usa sua classe interna Node<K, V> para armazenar mapeamentos. HashMap trabalha com algoritmo de hash e utiliza os métodos hashCode() e equals() na chave para operações de obtenção e inserção. HashMap utiliza uma lista encadeada simples para armazenar elementos, que são chamados de bins ou buckets. Quando chamamos o método put, o hashCode da chave é usado para determinar o bucket que será usado para armazenar o mapeamento. Uma vez identificado o bucket, o hashCode é utilizado para verificar se já existe uma chave com o mesmo hashCode ou não. Se houver uma chave existente com o mesmo hashCode, então o método equals() é usado na chave. Se equals retornar true, então o valor é sobrescrito, caso contrário, um novo mapeamento é feito para este bucket de lista encadeada simples. Se não houver chave com o mesmo hashCode, então o mapeamento é inserido no bucket. Para a operação get do HashMap, novamente o hashCode da chave é usado para determinar o bucket onde procurar o valor. Depois que o bucket é identificado, as entradas são percorridas para encontrar a Entrada usando o hashCode e o método equals. Se houver correspondência, o valor é retornado, caso contrário, null é retornado. Há muito mais coisas envolvidas, como o algoritmo de hash para obter o bucket da chave, rehashing dos mapeamentos etc. Mas para o nosso trabalho, apenas lembre-se de que as operações do HashMap trabalham na chave e uma boa implementação dos métodos hashCode e equals é necessária para evitar comportamentos indesejados. A imagem abaixo mostra a explicação das operações de get e put. Leitura recomendada: Importância dos métodos hashCode e equals em Java
Fator de carga do HashMap do Java
O fator de carga é usado para determinar quando o HashMap será rehashed e o tamanho do bucket será aumentado. O valor padrão do bucket ou capacidade é 16 e o fator de carga é 0,75. O limiar para o rehashing é calculado multiplicando a capacidade e o fator de carga. Portanto, o valor padrão do limiar será 12. Assim, quando o HashMap tiver mais de 12 mapeamentos, ele será rehashed e o número de bins será aumentado para o próximo número de potência 2, ou seja, 32. Observe que a capacidade do HashMap é sempre uma potência de 2. O fator de carga padrão de 0,75 fornece um bom equilíbrio entre espaço e complexidade de tempo. Mas você pode defini-lo para valores diferentes com base em sua necessidade. Se você quiser economizar espaço, pode aumentar o valor para 0,80 ou 0,90, mas as operações de get/put levarão mais tempo.
Java HashMap keySet
O método keySet do HashMap do Java retorna a visualização Set das chaves no HashMap. Esta visualização Set é suportada pelo HashMap e quaisquer alterações no HashMap são refletidas no Set e vice-versa. Abaixo está um programa simples que demonstra exemplos de keySet do HashMap e qual é a maneira de prosseguir se você quiser um keySet não suportado pelo mapa.
package com.journaldev.examples;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class HashMapKeySetExample {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("1", "1");
map.put("2", "2");
map.put("3", "3");
Set<String> keySet = map.keySet();
System.out.println(keySet);
map.put("4", "4");
System.out.println(keySet); // keySet is backed by Map
keySet.remove("1");
System.out.println(map); // map is also modified
keySet = new HashSet<>(map.keySet()); // copies the key to new Set
map.put("5", "5");
System.out.println(keySet); // keySet is not modified
}
}
A saída do programa acima deixará claro que o keySet é suportado pelo mapa.
[1, 2, 3]
[1, 2, 3, 4]
{2=2, 3=3, 4=4}
[2, 3, 4]
Valores do Java HashMap
O método de valores do Java HashMap retorna uma visualização da coleção dos valores no Mapa. Esta coleção é suportada pelo HashMap, então quaisquer mudanças no HashMap serão refletidas na coleção de valores e vice-versa. Um exemplo simples abaixo confirma esse comportamento da coleção de valores do HashMap.
package com.journaldev.examples;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class HashMapValuesExample {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("1", "1");
map.put("2", "2");
map.put("3", null);
map.put("4", null);
map.put(null, "100");
Collection<String> values = map.values();
System.out.println("map values = " + values);
map.remove(null);
System.out.println("map values after removing null key = " + values);
map.put("5", "5");
System.out.println("map values after put = " + values);
System.out.println(map);
values.remove("1"); // changing values collection
System.out.println(map); // updates in map too
}
}
A saída do programa acima está abaixo.
map values = [100, 1, 2, null, null]
map values after removing null key = [1, 2, null, null]
map values after put = [1, 2, null, null, 5]
{1=1, 2=2, 3=null, 4=null, 5=5}
{2=2, 3=null, 4=null, 5=5}
Conjunto de entradas do Java HashMap
O método entrySet do Java HashMap retorna a visualização do conjunto de mapeamentos. Este entrySet é suportado pelo HashMap, então quaisquer mudanças no mapa refletem no conjunto de entradas e vice-versa. Dê uma olhada no programa de exemplo abaixo para o exemplo de entrySet do HashMap.
package com.journaldev.examples;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class HashMapEntrySetExample {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("1", "1");
map.put("2", null);
map.put(null, "100");
Set<Entry<String,String>> entrySet = map.entrySet();
Iterator<Entry<String, String>> iterator = entrySet.iterator();
Entry<String, String> next = null;
System.out.println("map before processing = "+map);
System.out.println("entrySet before processing = "+entrySet);
while(iterator.hasNext()){
next = iterator.next();
System.out.println("Processing on: "+next.getValue());
if(next.getKey() == null) iterator.remove();
}
System.out.println("map after processing = "+map);
System.out.println("entrySet after processing = "+entrySet);
Entry<String, String> simpleEntry = new AbstractMap.SimpleEntry<String, String>("1","1");
entrySet.remove(simpleEntry);
System.out.println("map after removing Entry = "+map);
System.out.println("entrySet after removing Entry = "+entrySet);
}
}
Abaixo está a saída produzida pelo programa acima.
map before processing = {null=100, 1=1, 2=null}
entrySet before processing = [null=100, 1=1, 2=null]
Processing on: 100
Processing on: 1
Processing on: null
map after processing = {1=1, 2=null}
entrySet after processing = [1=1, 2=null]
map after removing Entry = {2=null}
entrySet after removing Entry = [2=null]
Java HashMap putIfAbsent
A simple example for HashMap putIfAbsent method introduced in Java 8.
package com.journaldev.examples;
import java.util.HashMap;
import java.util.Map;
public class HashMapPutIfAbsentExample {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("1", "1");
map.put("2", null);
map.put(null, "100");
System.out.println("map before putIfAbsent = "+map);
String value = map.putIfAbsent("1", "4");
System.out.println("map after putIfAbsent = "+map);
System.out.println("putIfAbsent returns: "+value);
System.out.println("map before putIfAbsent = "+map);
value = map.putIfAbsent("3", "3");
System.out.println("map after putIfAbsent = "+map);
System.out.println("putIfAbsent returns: "+value);
}
}
A saída do programa acima é;
map before putIfAbsent = {null=100, 1=1, 2=null}
map after putIfAbsent = {null=100, 1=1, 2=null}
putIfAbsent returns: 1
map before putIfAbsent = {null=100, 1=1, 2=null}
map after putIfAbsent = {null=100, 1=1, 2=null, 3=3}
putIfAbsent returns: null
Java HashMap forEach
package com.journaldev.examples;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
public class HashMapForEachExample {
public static void main(String[] args) {
Map map = new HashMap<>();
map.put("1", "1");
map.put("2", null);
map.put(null, "100");
BiConsumer action = new MyBiConsumer();
map.forEach(action);
// O método forEach da HashMap é introduzido no Java 8. É um método muito útil para realizar a ação fornecida para cada entrada no mapa até que todas as entradas tenham sido processadas ou a ação lance uma exceção.
System.out.println("\nHashMap forEach lambda example\n");
map.forEach((k,v) -> {System.out.println("Key = "+k+", Value = "+v);});
}
}
class MyBiConsumer implements BiConsumer {
@Override
public void accept(String t, String u) {
System.out.println("Key = " + t);
System.out.println("Processing on value = " + u);
}
}
// Exemplo de expressão lambda
Key = null
Processing on value = 100
Key = 1
Processing on value = 1
Key = 2
Processing on value = null
HashMap forEach lambda example
Key = null, Value = 100
Key = 1, Value = 1
Key = 2, Value = null
A saída do exemplo de programa acima usando forEach da HashMap é;
Java HashMap replaceAll
package com.journaldev.examples;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
public class HashMapReplaceAllExample {
public static void main(String[] args) {
Map map = new HashMap<>();
map.put("1", "1");
map.put("2", "2");
map.put(null, "100");
System.out.println("map before replaceAll = " + map);
BiFunction function = new MyBiFunction();
map.replaceAll(function);
System.out.println("map after replaceAll = " + map);
// O método replaceAll da HashMap pode ser usado para substituir o valor de cada entrada pelo resultado da invocação da função fornecida para essa entrada. Este método foi adicionado no Java 8 e podemos usar expressões lambda para o argumento deste método.
map.replaceAll((k, v) -> {
if (k != null) return k + v;
else return v;});
System.out.println("map after replaceAll lambda expression = " + map);
}
}
class MyBiFunction implements BiFunction {
@Override
public String apply(String t, String u) {
if (t != null)
return t + u;
else
return u;
}
}
// Substituir usando expressões lambda
map before replaceAll = {null=100, 1=1, 2=2}
map after replaceAll = {null=100, 1=11, 2=22}
map after replaceAll lambda example = {null=100, 1=111, 2=222}
A saída do programa acima usando replaceAll da HashMap é;
Java HashMap computeIfAbsent
package com.journaldev.examples;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
public class HashMapComputeIfAbsent {
public static void main(String[] args) {
Map map = new HashMap<>();
map.put("1", "10");
map.put("2", "20");
map.put(null, "100");
Function function = new MyFunction();
map.computeIfAbsent("3", function); //key not present
map.computeIfAbsent("2", function); //key already present
// O método computeIfAbsent da HashMap calcula o valor apenas se a chave não estiver presente no mapa. Após calcular o valor, ele é colocado no mapa se não for nulo.
map.computeIfAbsent("4", v -> {return v;});
map.computeIfAbsent("5", v -> {return null;}); //null value won't get inserted
System.out.println(map);
}
}
class MyFunction implements Function {
@Override
public String apply(String t) {
return t;
}
}
// Maneira de expressão lambda
{null=100, 1=10, 2=20, 3=3, 4=4}
Java HashMap computeIfPresent
O método computeIfPresent do Java HashMap recalcula o valor se a chave especificada estiver presente e o valor não for nulo. Se a função retornar nulo, o mapeamento é removido.
package com.journaldev.examples;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
public class HashMapComputeIfPresentExample {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("1", "10");
map.put("2", "20");
map.put(null, "100");
map.put("10", null);
System.out.println("map before computeIfPresent = " + map);
BiFunction<String, String, String> function = new MyBiFunction1();
for (String key : map.keySet()) {
map.computeIfPresent(key, function);
}
System.out.println("map after computeIfPresent = " + map);
map.computeIfPresent("1", (k,v) -> {return null;}); // mapping will be removed
System.out.println("map after computeIfPresent = " + map);
}
}
class MyBiFunction1 implements BiFunction<String, String, String> {
@Override
public String apply(String t, String u) {
return t + u;
}
}
O output produzido pelo exemplo do computeIfPresent do HashMap é;
map before computeIfPresent = {null=100, 1=10, 2=20, 10=null}
map after computeIfPresent = {null=null100, 1=110, 2=220, 10=null}
map after computeIfPresent = {null=null100, 2=220, 10=null}
Java HashMap compute
Se você deseja aplicar uma função em todos os mapeamentos com base em sua chave e valor, então o método compute deve ser usado. Se não houver mapeamento e este método for usado, o valor será nulo para a função de cálculo.
package com.journaldev.examples;
import java.util.HashMap;
import java.util.Map;
public class HashMapComputeExample {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("1", "1");
map.put("2", "2");
map.put(null, "10");
map.put("10", null);
System.out.println("map before compute = "+map);
for (String key : map.keySet()) {
map.compute(key, (k,v) -> {return k+v;});
}
map.compute("5", (k,v) -> {return k+v;}); //key not present, v = null
System.out.println("map after compute = "+map);
}
}
O output do exemplo de compute do HashMap é;
map before compute = {null=10, 1=1, 2=2, 10=null}
map after compute = {null=null10, 1=11, 2=22, 5=5null, 10=10null}
Java HashMap merge
Se a chave especificada não estiver presente ou estiver associada a nulo, então a associa com o valor não nulo fornecido. Caso contrário, substitui o valor associado pelos resultados da função de remapeamento fornecida, ou remove se o resultado for nulo.
package com.journaldev.examples;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
public class HashMapMergeExample {
public static void main(String[] args) {
Map map = new HashMap<>();
map.put("1", "1");
map.put("2", "2");
map.put(null, "10");
map.put("10", null);
for (Entry entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
//merge lança NullPointerException se a chave ou o valor for nulo
if(key != null && value != null)
map.merge(entry.getKey(), entry.getValue(),
(k, v) -> {return k + v;});
}
System.out.println(map);
map.merge("5", "5", (k, v) -> {return k + v;}); // key not present
System.out.println(map);
map.merge("1", "1", (k, v) -> {return null;}); // method return null, so remove
System.out.println(map);
}
}
O output do programa acima é;
{null=10, 1=11, 2=22, 10=null}
{null=10, 1=11, 2=22, 5=5, 10=null}
{null=10, 2=22, 5=5, 10=null}
Isso é tudo para HashMap em Java, espero que nada importante tenha sido deixado de fora. Compartilhe também com outros se você gostou. Referência: API Doc
Source:
https://www.digitalocean.com/community/tutorials/java-hashmap