Antes de iniciarmos os exemplos e explicações, é importante entender que o HashMap trabalha com o conceito de key-value pairs, ou seja, cada elemento de sua lista possui uma chave e valor associado, assim podemos realizar uma busca rápida pela chave que desejamos, sem precisar percorrer toda lista ou saber o index/posição que desejamos consultar.
Estrutura do HashMap
O HashMap implementa a interface Map T<K,V>, Cloneable e Serializable, mas o que importa para nós aqui é apenas que ele implementa Map. Perceba que a própria implementação do Map T<K,V> usa Generics para atribuir um key-value para a lista, em outras palavras, com o HashMap e Generics podemos especificamente dizer qual o tipo da nossa chave (string, int, double e etc) e o tipo do nosso valor, que obviamente podem diferir sem problema algum.
Capacidade Inicial e Load Factor
Duas propriedades muito importantes no HashMap, e pouco comentadas são: A capacidade inicial (initial size) e o fator de carregamento/refresh (load factor).
Saiba mais: Java Streams API - Trabalhando com coleções
Quando você instancia um HashMap a sua capacidade inicial é 16, ou seja, você consegue inserir até 16 elementos no Map, sem a necessidade de criar novas posições. Caso você deseje, também pode instanciar um HashMap com mais ou menos de 16 posições, fica a seu critério e análise.
O atributo Load Factor está intrinsecamente ligado ao tamanho do HashMap, não é a toa que estamos explicando os dois juntos. O load factor é um atributo que mensura em que momento o HashMap deve dobrar seu tamanho, ou seja, antes que você possa preencher as 16 posições, em algum momento o tamanho do HashMap irá dobrar de 16 para 32, vamos ver como na Listagem 1.
Listagem 1. Calculando Load Factor
/*
* Supondo que o load factor e o initial size sejam padrões,
* consideramos as variáveis abaixo.
*
* Vamos supor um cálculo genérico abaixo, para entender
* o funcionamento do load factor.
* É óbvio que este cálculo já está dentro da classe HashMap e
* você não precisa fazê-lo, apenas
* entendê-lo.
* */
//Tamanho Inicial da Lista
int initialSize = 16;
//Valor do Load Factor
double loadFactor = 0.75;
/*
* Aqui é o ponto mais importante do cálculo. Multiplicando
* o tamanho inicial pelo load factor temos um valor que corresponde
* ao tamanho máximo suportado pela lista.
*
* No nosso caso o resultado será igual a 12. Isso significa
* que ao inserirmos 12 elementos em nosso HashMap,
* a lista dobrará de tamanho, ou seja, terá tamanho = 32.
*
* Depois o load factor é calculado novamente para o
* novo tamanho (32) e assim sucessivamente.
*
* */
double sizeToRehash = initialSize * loadFactor;
Não é aconselhável mudar o valor do load factor, a não ser que você tenha certeza do que está fazendo, pois a cada duplicação da lista é realizado um "Rehash" na tabela que consequentemente requer mais processamento.
Aplicação
Entendendo a estrutura do HashMap e como usar adequadamente suas variáveis, podemos então partir para o uso na prática. Observe a Listagem 2.
Listagem 2. Usando HashMap com exemplo de Load Factor na prática
import java.util.HashMap;
import java.util.Map;
public class ExemploHashMap {
public static void main (String args[]){
/*
* Criamos nosso HashMap que irá armazenar um par chave-valor,
* sendo que tanto a chave como o valor deverá sempre ser do tipo
* "String". Pois especificamos isso no Generics
* através do <String,String>
*
* Atenção: Lembre-se que o tamanho inicial do HashMap é
* 16 e o load factor é 0.75,
* então quando nossa lista alcançar o tamanho 12,
* automaticamente ela dobrará para 32.
* Vamos ver isso acontecer.
* */
Map<String,String> example = new HashMap<String,String>();
/*
* Vamos adicionar alguns valores a nossa lista
* */
example.put( "K1", new String( "V1" ));
example.put( "K2", new String( "V2" ));
example.put( "K3", new String( "V3" ));
example.put( "K4", new String( "V4" ));
example.put( "K5", new String( "V5" ));
example.put( "K6", new String( "V6" ));
example.put( "K7", new String( "V7" ));
example.put( "K8", new String( "V8" ));
example.put( "K9", new String( "V9" ));
example.put( "K10", new String( "V10" ));
example.put( "K11", new String( "V11" ));
example.put( "K12", new String( "V12" ));
/*
* LIMITE DE INSERÇÃO.
* Aqui é o limite de acord com o load factor, ou seja,
* quando o elemento 13 for inserido ocorrerá um Rehash na nossa lista.
* */
example.put( "K13", new String( "V13" ));
System.out.println("Rehash ocorrendo agora !
Nosso HashMap terá tamanho igual a 32 a partir daqui");
example.put( "K14", new String( "V14" ));
example.put( "K15", new String( "V15" ));
example.put( "K16", new String( "V16" ));
}
}
Na Listagem 2 acima podemos perceber principalmente duas coisas muito importantes:
- Usamos o método put() para inserir um novo par de elementos em nossa lista;
- O “rehash” só ocorre depois da inserção do elemento 13, isso porque mesmo o limite sendo 12 não é necessário ainda realizar o “rehash”, pois pode ser que paremos por ali e o “rehash” terá sido desnecessário.
Vamos ver outros métodos do HashMap, como é o caso do “containsKey”. Este método irá procurar por uma chave dentro da tabela, ou seja, o valor da chave que você especificar neste método deve ser do mesmo tipo especificado em HashMap T<K,V>. Observe a Listagem 3.
Listagem 3. Usando constainsKey para buscar elementos
import java.util.HashMap;
import java.util.Map;
public class ExemploHashMap {
public static void main (String args[]){
Map<String,String> example = new HashMap<String,String>();
/*
* Vamos adicionar alguns valores a nossa lista
* */
example.put( "K1", new String( "V1" ));
example.put( "K2", new String( "V2" ));
example.put( "K3", new String( "V3" ));
example.put( "K4", new String( "V4" ));
example.put( "K5", new String( "V5" ));
String keyToSearch = "K1";
if ( example.containsKey( keyToSearch ) ) {
System.out.println("Valor da Chave "+keyToSearch+
" = "+example.get(keyToSearch));
}else{
System.err.println("Chave não existe");
}
}
}
Foreach com HashMap
Caso você deseje percorrer todos os elementos do Map, você pode utilizar um foreach. Veja na Listagem 4 um exemplo disto.
Listagem 4. Usando foreach para percorrer elementos HashMap
import java.util.HashMap;
import java.util.Map;
public class ExemploHashMap {
public static void main(String args[]) {
Map<String, String> example = new HashMap<String, String>();
example.put("K1", new String("V1"));
example.put("K2", new String("V2"));
example.put("K3", new String("V3"));
example.put("K4", new String("V4"));
example.put("K5", new String("V5"));
/*
* O método "keySet()" retorna um Set com todas as chaves do
* nosso HashMap, e tendo o Set com todas as Chaves,
* podemos facilmente pegar
* os valores que desejamos
* */
for (String key : example.keySet()) {
//Capturamos o valor a partir da chave
String value = example.get(key);
System.out.println(key + " = " + value);
}
}
}
Criando Objetos Customizados
É muito importante ficar atento a alguns aspectos quando deseja-se utilizar objetos customizados como valores ou chaves no seu HashMap, objetos como: Um DTO de Funcionario, PessoaFisica e assim por diante.
Dois métodos são obrigatórios: equals() e hashCode(), caso estes não sejam implementados adequadamente seu HashMap terá sérios problemas de busca e organização. Veja na Listagem 5 uma implementação de exemplo.
Listagem 5. Criando DTO com equals() e hashCode()
import java.io.Serializable;
public class DTOPadrao implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private int id;
public boolean equals(Object obj) {
if (obj == null)
return false;
if (this.getClass() != obj.getClass())
return false;
String name = obj.toString();
return this.toString().equals(name);
}
public int hashCode() {
return this.toString().hashCode();
}
public String toString() {
return new StringBuilder
(this.getClass().getName()).append("#")
.append(this.getId()).toString();
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
O objetivo principal deste artigo foi demonstrar o uso do HashMap na prática e alguns conceitos muito importantes de pouco conhecidos, como é o caso do load factor. Em algumas situações é importante ter noção do que está acontecendo por “trás dos panos”. Imagine uma HashMap com 300 mil elementos e load factor 0,75, em algum momento esses 300 mil elementos irão se transformar em 600 mil e exigir muito processamento, assim você pode trabalhar com o load factor para atrasar esse “rehash” e ganhar performance.
O uso do HashMap é muito comum quando trabalhamos com valores “nomeados”, ou seja, não nos importa a posição do item mas sim o valor da sua chave. Um local onde o HashMap é utilizado com muita frequência é na parametrização de métodos, ou seja, imagine que você tem um método que pode receber um número diversificado de parâmetros cada um com nomes distintos, você pode usar o HashMap com o conceito de chave = nome do parâmetro e valor = valor do parâmetro.
Links Úteis
- Como conectar ao PostgreSQL no Entity Framework:
Neste conteúdo você aprenderá a utilizar o Entity Framework em conjunto com o banco de dados PostgreSQL. - $_POST e $_GET: Acessando informações de formulários em PHP:
Neste conteúdo você aprenderá a acessar informações passadas por formulários no PHP com as variáveis globais $_GET e $_POST. - PHP Exceptions: Trabalhando com exceções em PHP:
Neste curso você aprenderá a lidar com exceções em suas aplicações PHP Veremos como o programa se comporta ao ser lançada uma exceção
Saiba mais sobre Java ;)
- Carreira Programador Java:
Nesse Guia de Referência você encontrará o conteúdo que precisa para iniciar seus estudos sobre a tecnologia Java, base para o desenvolvimento de aplicações desktop, web e mobile/embarcadas. - Linguagem Java:
Neste Guia de Referência você encontrará todo o conteúdo que precisa para começar a programar com a linguagem Java, a sua caixa de ferramentas base para criar aplicações com Java. - Spring Framework:
Neste Guia de Referência você encontrará o conteúdo que precisa para aprender a desenvolver aplicações utilizando o Spring Framework e seus subprojetos.