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:

  • 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

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.