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.