HashMap Java: Trabalhando com Listas key-value

Veja neste artigo detalhes do funcionamento do HashMap em Java, assim como seu atributo Load Factor, que é pouco comentado e pouco usado.

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

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:

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

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.

Artigos relacionados