Neste artigo veremos como criar uma forma prática de parametrização em Java, convertendo uma lista de parâmetros, com tamanho indefinido, para um Map de fácil manipulação. Antes vamos entender o cenário do nosso problema para saber a real necessidade de utilização deste recurso.

É muito comum, principalmente quando estamos trabalhando com NamedQueries do JPA, a passagem de parâmetros de quantidade indefinida, sem nomeação ou tipagem específica. Isso nos da a possibilidade de criar métodos genéricos o suficiente para executar qualquer Query do JPA, independente da quantidade de parâmetros exigida ou mesmo do tipo de cada parâmetro. Vamos supor, por exemplo, os seguintes HQL's:

  1. “SELECT * FROM Pessoa p WHERE p.nome = :nome AND p.idade = :idade
  2. “SELECT * FROM Produto p WHERE p.valor = :valor AND p.dataValidade = :dataValidade

Temos 2 HQL's acima, cada um com dois parâmetros mas com tipos e nomes distintos. A solução mais elegante e correta seria criar um método recebendo um Map de parâmetros como o Generic . Assim conseguimos trabalhar de forma genérica suficiente e não precisamos criar um método para cada tipo de HQL que surgir.

É muito comum projetos trabalharem recebendo um Map de Parâmetros, tornando a produtividade muito melhor, e não só para o HQL, mas para todas as outras situações em que os parâmetros devem ser abstraídos. Porém, a forma com que esses parâmetros são passados é que diferem de projeto para projeto, conforme veremos como neste artigo.

Baseado na Listagem 1 temos um método simples que recebe um Map de parâmetros. Vamos ver comumente como este Map pode ser passado.


import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
 
 
public class MyApp {
       
       public static void main (String[] args){
             /*
              * Vamos começar com um exemplo usando a forma comum 
              de passar um Map de params
              * para um método.
              * */
             
             Map<String,Integer> mapParams = new HashMap<String,Integer>();
             
             //Vamos supor a criação de alguns parâmetros, apenas para exemplo
             mapParams.put("idadeMinima", 15);
             mapParams.put("idadeMaxima", 60);
             mapParams.put("quantidadeMaxima", 450);
             mapParams.put("quantidadeMinima", 1);
             mapParams.put("fatorDeDistribuicao", 3);
             
             mostraParametrizacao(mapParams);
       }
       
       
       private static void mostraParametrizacao(Map<String,Integer> params){
             for(Entry<String, Integer> entry : params.entrySet()){
                    String key = entry.getKey();
                    Integer value = entry.getValue();
                    System.out.println("Key = "+key+", Value = "+value);
             }
       }
       
}
Listagem 1. Passando um Map de Parâmetros

Normal, certo? Qualquer um faria a mesma coisa, mas em um projeto com milhares de linhas onde a utilização de parâmetros via Map é muito frequente. Você vai rapidamente perceber que ficar criando um HashMap e usando put toda vez irá demandar muito tempo e tornar seu código muito poluído visualmente. Veja na Listagem 2 qual seria o ideal para situações de frequente utilização deste recurso.


import java.util.Map;
import java.util.Map.Entry;
 
 
public class MyApp {
       
   public static void main (String[] args){           
         
     mostraParametrizacao(new MapParams("idadeMinima", 15,
       "idadeMaxima", 60,
       "quantidadeMaxima", 450,
       "quantidadeMinima", 1,
       "fatorDeDistribuicao", 3));
   }
   
   
   private static void mostraParametrizacao(Map<String,Object> params){
     for(Entry<String, Object> entry : params.entrySet()){
        String key = entry.getKey();
        Object value = entry.getValue();
        System.out.println("Key = "+key+", Value = "+value);
     }
   }
       
}
Listagem 2. Uso adequado

Reduzimos consideravelmente a complexidade e a quantidade de linhas da nossa classe, veja como ficou simples e prático realizar a passagem de parâmetros para Map. Apenas tivemos que mudar o tipo do nosso Map de para , por exigência da nossa classe MapParams que torna tudo o mais genérico possível. Veremos mais a frente detalhes disso.

Então neste ponto vimos a facilidade que este artigo está propondo a você leitor, o uso constante de parâmetros via Map com uma simples classe que irá gerenciar tudo isso. Vamos utilizar a “Reusabilidade” ao nosso favor.

A classe MapParams

Entendido o problema principal, vamos agora aprofundar-nos na classe MapParams, afinal é ela quem fará toda a “mágica” de conversão sem que precisemos nos preocupar com instanciação de HashMap a cada momento. Na Listagem 3 nossa classe MapParams está toda comentada, linha a linha, facilitando assim a compreensão da mesma.


import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/*
* Nossa classe MapParams é responsável por converter nossos parâmetros
* para um Map, assim podemos realizar operações comuns como clear, contains, 
* put e assim por diante.
*
* */
public class MapParams implements Map<String, Object>, Serializable {
     
     private static final long serialVersionUID = 1L;
     
     /*
      * Este é o objetivo convertido, ou seja, é neste objeto que todos os parâmetros serão
      * mapeados e inseridos. Toda operação final será realizada com este objeto.
      * */
     private Map<String, Object> paramsAsMap;

     /*
      * Nosso construtor recebe uma lista indefinida de parâmetros e chama o método
      * "inserirParametros(parametros)" que é responsável por realizar 
      * explicitamente a conversão para Map.
      * */
     public MapParams(Object... parametros) {
           inserirParametros(parametros);
     }
     
     @SuppressWarnings("unchecked")
     public MapParams inserirParametros(Object... parametros) {
           
           /*
            * Como trata-se de um mapeamento 1 para 1, onde cada String (key) 
             * terá seu respectivo valor (value), formando um par 'key-value',
             * então é obrigatório que a quantidade de parâmetros seja par.
            * */
           if (parametros.length % 2 != 0)
                  throw new IllegalArgumentException
                    ("Espera-se número par de objetos: " + parametros.length);
           
           /*
            * Verificamos se nosso objeto Map já foi criado, caso contrário 
            * criamos um novo Map com a mesma quantidade dos parâmetros 
             * passados divido por 2, isso porque a cada 2 valores formamos
            * 1 novo item no Map. Lembre-se do par key-value, 
             * isso define como nosso Map funciona.
            * */
           if (this.paramsAsMap == null)
                  this.paramsAsMap = new HashMap(parametros.length / 2);
           
           
           /*
            * Neste ponto a conversão começa. Criamos um laço for de zero (0) até
            * a quantidade de parâmetros, incrementando de 2 em 2.
            * */
           for (int i = 0; i < parametros.length; i += 2) {
                  
                  /*
                   * Como nosso laço irá incrementar de 2 em 2 e começamos do zero, 
                   * então sempre pegaremos um valor par, que corresponde ao 
                    * 'key' do Map, e o valor impar corresponde ao
                   * 'value' do Map.
                   * Por exemplo: {'NOME', 'Devmedia','TIPO','Empresa'}.
                    * Na lista ao lado temos as keys NOME e TIPO,
                   * e os seus respectivos valores 'Devmedia' e 'Empresa'.
                    * Perceba que sempre o Key é par e o Value
                   * é impar.
                   *
                   * */
                  Object key = parametros[i];
                  
                  /*
                   * Por obrigação nosso 'Key' tem que ser sempre uma String, 
                    * não faz sentido que seja de outra forma.
                   * Então verificamos de o objeto key é uma instancia da classe String, 
                    * caso contrário ele
                   * lançará uma exceção abortando a continuação da conversão.
                   * */
                  if (! (key instanceof String))
                         throw new IllegalArgumentException("O parametro '" + i + "' 
                           deveria ser uma String.");
                  
                  
                  /*
                   * Caso o valor de i+1 seja menor que a quantidade total 
                    * de parâmetros, então retornamos o valor do parâmetro no índice i+1, 
                    * que é igual a um valor impar, já que nosso i sempre será par,
                   * e um valor impar corresponde sempre ao nosso 'value' no Map.
                   * Retornado o 'value', agora já temos o objeto key e o 
                    * objeto value, basta usarmos o 'put'
                   * para inserir esse par em nosso objeto Map.
                   * */
                  Object value = (i + 1 < parametros.length ? parametros[i + 1] : null);
                  paramsAsMap.put(key.toString(), value);               
           }
           

           /*
            * Neste ponto, toda conversão já está pronta, então o que 
             * retornaremos é a classe MapParams,
            * e não apenas o objeto paramAsMap.
            * */
           return this;
     }
     
     /*
      * Todos os métodos abaixo são padrões do Map, e como o mesmo é uma Interface, somos
      * obrigados a implementá-los.
      * */
     
     public void clear() {
           paramsAsMap.clear();              
     }

     public boolean containsKey(Object key) {
           return paramsAsMap.containsKey(key);
     }

     public boolean containsValue(Object value) {
           return paramsAsMap.containsValue(value);
     }

     public Set<Entry<String, Object>> entrySet() {
           return paramsAsMap.entrySet();
     }

     public Object get(Object key) {
           return paramsAsMap.get(key);
     }

     public boolean isEmpty() {
           return paramsAsMap.isEmpty();
     }

     public Set<String> keySet() {
           return paramsAsMap.keySet();
     }

     public Object put(String key, Object value) {
           return paramsAsMap.put(key, value);
     }

     public void putAll(Map<? extends String, ? extends Object> t) {
           paramsAsMap.putAll(t);
     }

     public Object remove(Object key) {
           return paramsAsMap.remove(key);
     }

     public int size() {
           return paramsAsMap.size();
     }

     public Collection<Object> values() {
           return paramsAsMap.values();
     }

     @Override
     public String toString() {
           return paramsAsMap.toString();
     }
}
Listagem 3. MapParams.java

O principal objetivo deste artigo foi primeiramente demonstrar o cenário onde o problema é aplicado e a solução deste problema utilizando uma classe chamada MapParams, que pode ser nomeada para um nome de sua escolha. Sendo assim, aumentamos a Reusabilidade de código e consequentemente a produtividade do projeto.