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:
- “SELECT * FROM Pessoa p WHERE p.nome = :nome AND p.idade = :idade
- “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);
}
}
}
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);
}
}
}
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();
}
}
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.