terça-feira, 20 de outubro de 2009

VRaptor 3 - Poupando recursos - LAZY Dependence Injection

Objetivo: Ao final deste artigo espera-se que você saiba como poupar recursos caros, trazendo eles de forma LAZY, ou como prefiro chamar Just-in-Time (no momento certo).

No VRaptor3 a injeção de dependencia ficou bem mais fácil, os interceptadores que eram os responsáveis para injetar a dependencia sumiram e agora fica tudo a cargo do container, que pode ser o Spring ou o Pico.

A facilidade na injeção de dependencia tem um custo, como não é mais controlado pelo programador que cria o interceptor sempre que declaramos uma dependencia no construtor de um @Component, @Resource ou @Intercepts ele é injetado no inicio, logo na construção, porem as vezes o fluxo de um requisição faz com que não usemos algumas destas injeções de dependencia, disperdiçando recursos valiosos.

Por exemplo, vamos supor o seguinte @Resource abaixo, que cadastra produtos

  1. import java.util.List;
  2.  
  3. import org.hibernate.Session;
  4.  
  5. import br.com.caelum.vraptor.Result;
  6. import br.com.caelum.vraptor.view.Results;
  7.  
  8. public class ProdutoController {
  9.         /**
  10.          * O recurso que queremos poupar.
  11.          */
  12.         private final Session session;
  13.         private final Result result;
  14.        
  15.         public ProdutoController(final Session session, final Result result) {
  16.                 this.session = session;
  17.                 this.result = result;
  18.         }
  19.        
  20.         /**
  21.          * apenas renderiza o formulário
  22.          */
  23.         public void form() {}
  24.        
  25.         public List<Produto> listar() {
  26.                 return session.createCriteria(Produto.class).list();
  27.         }
  28.        
  29.         public Produto adiciona(Produto produto) {
  30.                 session.persist(produto);
  31.                 result.use(Results.logic()).redirectTo(getClass()).listar();
  32.                 return produto;
  33.         }
  34. }


Sempre que alguem faz uma requisição a qualquer lógica dentro do recurso ProdutoController uma Session é aberta, porem note que abrir o formulário para adicionar produtos não requer sessão com o banco, ele apenas renderiza uma página, cada vez que o formulário de produtos é aberto um importante e caro recurso do sistema esta sendo requerido, e de forma totalmente ociosa.

Como agir neste caso ? isolar o formulário poderia resolver este problema mais recairia em outro, da mantenabilidade.

O ideal é que este recurso só fosse realmente injetado no tempo certo (Just in Time) como seria possivel fazer isso ? a solução é usar proxy dinamicos, enviando uma session que só realmente abrirá a conexão com o banco quando um de seus métodos for invocado

  1. import java.lang.reflect.Method;
  2.  
  3. import javax.annotation.PreDestroy;
  4. import org.hibernate.classic.Session;
  5. import org.hibernate.SessionFactory;
  6.  
  7. import net.vidageek.mirror.dsl.Mirror;
  8.  
  9. import br.com.caelum.vraptor.ioc.ApplicationScoped;
  10. import br.com.caelum.vraptor.ioc.Component;
  11. import br.com.caelum.vraptor.ioc.ComponentFactory;
  12. import br.com.caelum.vraptor.ioc.RequestScoped;
  13. import br.com.caelum.vraptor.proxy.MethodInvocation;
  14. import br.com.caelum.vraptor.proxy.Proxifier;
  15. import br.com.caelum.vraptor.proxy.SuperMethod;
  16.  
  17. /**
  18.  * <b>JIT (Just-in-Time) {@link Session} Creator</b> fábrica para o componente {@link Session}
  19.  * gerado de forma LAZY ou JIT(Just-in-Time) a partir de uma {@link SessionFactory}, que
  20.  * normalmente se encontra em um ecopo de aplicativo @{@link ApplicationScoped}.
  21.  *
  22.  * @author Tomaz Lavieri
  23.  * @since 1.0
  24.  */
  25. @RequestScoped
  26. public class JITSessionCreator implements ComponentFactory<Session> {
  27.        
  28.     private static final Method CLOSE = new Mirror().on(Session.class).reflect().method("close").withoutArgs();
  29.     private static final Method FINALIZE = new Mirror().on(Object.class).reflect().method("finalize").withoutArgs();
  30.     private final SessionFactory factory;
  31.     /** Guarda a Proxy Session */
  32.         private final Session proxy;
  33.         /** Guarada a Session real. */
  34.         private Session session;
  35.        
  36.         public JITSessionCreator(final SessionFactory factory, final Proxifier proxifier) {
  37.                 this.factory = factory;
  38.                 this.proxy = proxify(Session.class, proxifier); // *1*
  39.         }
  40.        
  41.         /**
  42.          * Cria o JIT Session, que repassa a invocação de qualquer método, exceto
  43.          * {@link Object#finalize()} e {@link Session#close()}, para uma session real, criando
  44.          * uma se necessário.
  45.          */
  46.         private Session proxify(Class<? extends Session> target, Proxifier proxifier) {
  47.                 return proxifier.proxify(target, new MethodInvocation<Session>() {
  48.                     @Override // *2*
  49.                     public Object intercept(Session proxy, Method method, Object[] args, SuperMethod superMethod) {
  50.                         if (method.equals(CLOSE) || (method.equals(FINALIZE) && session == null)) {
  51.                             return null; //skip
  52.                         }
  53.                         return new Mirror().on(getSession()).invoke().method(method).withArgs(args);
  54.                     }
  55.                 });
  56.         }
  57.        
  58.         public Session getSession() {
  59.                 if (session == null) // *3*
  60.                         session =       factory.openSession();
  61.                 return session;
  62.         }
  63.        
  64.         @Override
  65.         public Session getInstance() {
  66.                 return proxy; // *4*
  67.         }
  68.        
  69.         @PreDestroy
  70.         public void destroy() { // *5*
  71.                 if (session != null && session.isOpen()) {
  72.                         session.close();
  73.                 }
  74.         }
  75. }


Explicando alguns pontos chaves, comentados com // *N*
  1. O Proxfier é um objeto das libs do vrapor que auxilia na criação de objetos proxys ele é responsável por escolher a biblioteca que implementa o proxy dinamico, e então invocar via callback um método interceptor, como falamo abaixo.

  2. Neste ponto temos a implementação do nosso interceptor, sempre que um método for envocado em nosso proxy, esse intereptor é invocado primeiro, ele filtra as chamada ao método finalize caso a session real ainda não tenha sido criada, isso evita criar a session apenas para finaliza-la.
    O método close também é filtrao, isso é feito para evitar criar uma session apenas para fecha-la, e também por que o nosso SessionCreator é que é o responsavel por fechar a session ao final do scopo, quando a request acabar.
    Todos os outros métodos são repassados para uma session através do método getSession() onde é realmente que acontece o LAZY ou JIT.

  3. Aqui é onde acontece a mágia, da primeira vez que getSession() é invocado a sessão é criada, e então repassada, todas as outras chamadas a getSession() repassam a sessão anteriormente criada, assim, se getSession() nunca for envocado, ou seja, se nenhum método for envocado no proxy, getSession() nunca será invocado, e a sessão real não será criada.

  4. O retorno desse ComponentFactory é a Session proxy, que só criará a session real se um de seus métodos for invocado.

  5. Ao final do escopo o destroy é invocado, ele verifica se a session real existe, existindo verifica se esta ainda esta aberta, e estando ele fecha, desta forma é possivel garantir que o recurso será sempre liberado.


Assim podemos agora pedir uma session sempre que acharmos que vamos precisar de uma, sabendo que o recurso só será realmente solicitado quando formos usar um de seus métodos, salvando assim o recurso.

Esta mesma abordagem pode ser usada para outros recursos caros do sistema.

Os códigos fonte para os ComponentFactory de EntityManager e Session que utilizo podem ser encontrados neste link: http://guj.com.br/posts/list/141500.java

sábado, 17 de outubro de 2009

VRaptor3 - Interceptando recursos anotados

Em primeira lugar, gostaria de tecer meus mais sinceros elogios a equipe VRaptor, a versão 3 esta muito boa, bem mais intuitiva e fácil de usar que a 2.6

Neste artigo vou mostar como interceptar um método de um Resource especifico, identificando-o a partir de uma anotação e executar ações antes do método executar, e após ele executar.

Vamos supor que nós temos o seguinte Resource para adicionar produtos no nosso sistema


  1. @Resource
  2. public class ProdutoController {
  3.         private final DaoFactory factory;
  4.         private final ProdutoDao dao;
  5.        
  6.         public ProdutoController(DaoFactory factory) {
  7.                 this.factory = factory;
  8.                 this.dao = factory.getProdutoDao();
  9.         }
  10.        
  11.         public List<Produto> listar() {
  12.                 return dao.list();
  13.         }
  14.        
  15.         public Produto atualizar(Produto produto) {
  16.                 try {
  17.                         factory.beginTransaction();
  18.                         produto = dao.update(produto);
  19.                         factory.commit();
  20.                         return produto;
  21.                 } catch (DaoException ex) {
  22.                         factory.rollback();
  23.                         throw ex;
  24.                 }
  25.         }
  26.        
  27.         public Produto adicionar(Produto produto) {
  28.                 try {
  29.                         factory.beginTransaction();
  30.                         produto = dao.store(produto);
  31.                         factory.commit();
  32.                         return produto;
  33.                 } catch (DaoException ex) {
  34.                         factory.rollback();
  35.                         throw ex;
  36.                 }
  37.         }
  38. }


Agora nos queremos que um interceptador intercepte meu recurso, e execute a lógica dentro de um escopo transacional, como fazer isso ? é só criar um interceptador assim.

  1. import org.hibernate.Session;
  2. import org.hibernate.Transaction;
  3.  
  4. import br.com.caelum.vraptor.Intercepts;
  5. import br.com.caelum.vraptor.core.InterceptorStack;
  6. import br.com.caelum.vraptor.interceptor.Interceptor;
  7. import br.com.caelum.vraptor.resource.ResourceMethod;
  8.  
  9. @Intercepts
  10. public class TransactionInterceptor implements Interceptor {
  11.         private final Session session;
  12.         public TransactionInterceptor(Session session) {
  13.                 this.session = session;
  14.         }
  15.         public void intercept(InterceptorStack stack, ResourceMethod method, Object instance) {
  16.                 Transaction transaction = null;
  17.                 try {
  18.                         transaction = session.beginTransaction();
  19.                         stack.next(method, instance);
  20.                         transaction.commit();
  21.                 } finally {
  22.                         if (transaction != null && transaction.isActive()) {
  23.                                 transaction.rollback();
  24.                         }
  25.                 }
  26.         }
  27.         public boolean accepts(ResourceMethod method) {
  28.                 return true; //aceita todas as requisições
  29.         }
  30. }


Ok, o interceptador vai rodar e abrir transação antes e depois de executar a logica, e os métodos transacionais da minha lógica irão se reduzir a isto

  1. public Produto atualizar(Produto produto) {
  2.         return dao.update(produto);
  3. }
  4.  
  5. public Produto adicionar(Produto produto) {
  6.         return dao.store(produto);
  7. }


Ok mas, neste caso temos o problema de que métodos que não exigem transação estão abrindo e fechando transação a cada requisição sem necessidade.

Como então selecionar apenas algumas lógicas para serem transacionais ? podem criar uma anotação para isto, desta forma:

  1. import java.lang.annotation.ElementType;
  2. import java.lang.annotation.Retention;
  3. import java.lang.annotation.RetentionPolicy;
  4. import java.lang.annotation.Target;
  5.  
  6. /**
  7.  * Usado para garantir que um determinado recurso interceptado seja executada em um
  8.  * escopo de tranzação.
  9.  * @author Tomaz Lavieri
  10.  * @since 1.0
  11.  */
  12. @Retention(RetentionPolicy.RUNTIME)
  13. @Target({ElementType.METHOD,ElementType.TYPE})
  14. public @interface Transactional {}


Agora precisamos marcar os pontos onde queremos que o escopo seja transacional com esta anotação.

  1. @Resource
  2. public class ProdutoController {
  3.         private final ProdutoDao dao;
  4.        
  5.         public ProdutoController(ProdutoDao dao) {
  6.                 this.dao = dao;
  7.         }
  8.        
  9.         public List<Produto> listar() {
  10.                 return dao.list();
  11.         }
  12.        
  13.         @Transactional
  14.         public Produto atualizar(Produto produto) {
  15.                 return dao.update(produto);
  16.         }
  17.        
  18.         @Transactional
  19.         public Produto adicionar(Produto produto) {
  20.                 return dao.store(produto);
  21.         }
  22. }


Ok o código ficou bem mais enxuto, mas como interceptar apenas os métodos marcados com esta anotação ?? para tal basta no nosso accepts do TransactionInterceptor verificarmos se a anotação esta presente no método, ou no proprio rescurso (quando marcado no recurso todos os métodos do recurso seriam transacionais).

A modificação do método ficaria assim

  1.         public boolean accepts(ResourceMethod method) {
  2.                 return  method
  3.                                 .getMethod() //metodo anotado
  4.                                 .isAnnotationPresent(Transactional.class)
  5.                         || method
  6.                                 .getResource() //ou recurso anotado
  7.                                 .getType()
  8.                                 .isAnnotationPresent(Transactional.class);
  9.         }


Pronto agora somente os métodos com a anotação @Transacional são executados em escopo de transação e economisamos linhas e linhas de códigos de try{commit}catch{rollback throw ex}

sábado, 19 de setembro de 2009

Apresentação Java Collection

No dia 18/09/2009 falei no SUN DAY da Semana de computação da UFPB sobre Java Collection.

Os arquivos usados na apresentação seguem logo abaixo.


Collections: Apresentação usada no Sun Day
Diagrama de Escolha
Diagrama de Agrupamento

Apresentação original, feita para o grupo de estudos
Collections e Generics: Apresentação no Grupo de estudos

meu twitter:
http://twitter.com/tomazlavieri

Fotos do evento:





sexta-feira, 17 de julho de 2009

Collection e Generics

Ultima apresentação que fiz para o Grupo de SCJP referencte a Collections e Generics

Em breve posto mais detalhes.

para baixar a apresentação sem se cadastrar no SlideShare slides em pdf

quinta-feira, 4 de junho de 2009

SCJP - Exercicio sobre Garbage Collector

A pedido de um colega, fiz um pequeno Slide, mostrando como resolver exercícios para a Certificação SCJP 6 sobre Garbage Collector.


O pdf pode ser baixado aqui.

Obs.: prometo que muito em breve dou uma revisada, para melhorar o portugues da apresentação.

terça-feira, 2 de junho de 2009

SCJP 6.0 passei com 90%

Bom pessoal, passei os últimos 45 dias, ocupado, escutando para o SCJP e por isso não postei mais, a partir de agora, pretendo retomar os posts... segue um pequeno resumo da trajetória que fiz para a certificação.

Fiz hoje cedo o exame em recife,

foi bem tranquilo, tirando que não tive muito paciencia de fazer a revisão, e devo ter errado em questões por desatenção, pois anotei todas as questões que tive duvida, real, e depois da prova conferi que minhas suposições foram corretas ^^... 

Fiz o score de 90% divido conforme abaixo... 

  1. /* 
  2. - Declarations, Initialization and Scoping .... 91% 
  3. - Flow Control ................................ 90% 
  4. - API Contents ................................ 90% 
  5. - Concurrency ................................ 100% 
  6. - OO Concepts ................................ 100% 
  7. - Collections / Generics ...................... 90% 
  8. - Fundamentals ................................ 72% 
  9. */  


Comecei a esudar 14/04/2009 quando comprei o livro => http://www.guj.com.br/posts/list/123655.java 

Estudei pelo livro da Kathy, SCJP 6, fiz todos os exercicios 

cap 01 -> . 100% 
cap 02 -> .. 80% 
cap 03 -> . 100% 
cap 04 -> .. 90% 
cap 05 -> .. 94% 
cap 06 -> .. 80% 
cap 07 -> .. 94% 
cap 08 -> .. 92% 
cap 09 -> .. 94% 
cap 10 -> .. 83% 

......... 
Ai fiz um Mock (bem fraco por sinal) do javaranch, que tem varias questões fora do escopo da prova, que acertei 92% 
http://www.javaprepare.com/quests/test.html 

Ai fiz o exame do Inquisition-0.14 ... o de 82 questões, acertei 94% .. fiz os outros exames menores também, que acertei por volta de 90% 

Depois fiz 2 provas sorteadas no Testkiller (72 questões cada prova) 92% na primeira, e 96% na segunda 

em seguida nos dois dia antes do exame fiz 2 provas do ExamLab for SCJP6, que achei o melhor dos exames, principalmente, pq é muito semelhante a prova, o mais proximo a prova original... tirei + de 94% na primeira e 92% na segunda... (a engine é semelhante, mais o nivel é mais dificil)... 

e hoje pela manha fiz a prova, enfim, agora é batalhar por experiencia profissional, e passar para as proximas certificações 

e foi isso

quinta-feira, 2 de abril de 2009

Igualdade em java - equals() e hashCode()

Um dos maiores problemas que os programadores encontram ao inciar seu trabalho em java é o de entender os testes de igualdade.

A igualdade em java não pode ser testada através do operador == , estes operador apenas verifica se duas variaveis fazem referencia a mesma instancia de um objetos.

Para ser um pouco mais claro, imaginemos uma classe Pessoa, como segue abaixo.

  1. public class Pessoa {
  2.     private String nome;
  3.     private String cpf;
  4.     private Integer idade;
  5.    
  6.     public Pessoa(String nome, String cpf) {
  7.         this.nome = nome;
  8.         this.cpf = cpf;
  9.     }
  10.    
  11.     public Integer getIdade() {
  12.         return idade;
  13.     }
  14.    
  15.     public void setIdade(Integer idade) {
  16.         this.idade = idade;
  17.     }
  18.  
  19.     public String getNome() {
  20.         return nome;
  21.     }
  22.    
  23.     public String getCpf() {
  24.         return cpf;
  25.     }
  26. }


Como posso eu falar que duas pessoas são as mesmas pessoas ? a JVM (Java Virutal Machine) por se só não tem como identificar como duas pessoas podem ser ditas iguais, desta forma o teste abaixo, verifica apenas se estas são instancias de um mesmo objeto.

  1. Pessoa maria = new Pessoa("Maria da Silva","111.222.333-44");
  2. maria.setIdade(15);
  3.  
  4. Pessoa mariaDaSilva = new Pessoa("Maria da Silva","333.555.222-11");
  5. mariaDaSilva.setIdade(40);
  6.  
  7. //apesar dos nomes iguais de maria e mariaDaSilva, os cpfs são
  8. //diferentes e desta forma posso falar que são pessoas diferentes
  9.  
  10. Pessoa mariaDaSilva2 = new Pessoa("Maria da Silva","333.555.222-11");
  11.  
  12. //mariaDaSilva e mariaDaSilva2 são pessoas iguais, e mesmo não setando
  13. //a idade de mariaDaSilva2, em um teste de igualdade eu vou querer que
  14. //para esses 2 objetos ele seja positivo.
  15.  
  16. //porem são 2 instancias de de objetos diferentes, portanto
  17.  
  18. boolean referencia = mariaDaSilva == mariaDaSilva2;
  19.  
  20. System.out.println("Referencia é falso (" + referencia + ")");
  21. //isso ocorre pq:
  22. //a variável mariaDaSilva aponta para um objeto enquanto
  23. //a variável mariaDaSilva2 aponta para outro objeto.


Equals:

Para resolver esse problema em java existe um mecanismo de igualdade que vem em todo objeto, e descende da classe Object, onde vc pode testar a igualdade entre objetos através de: primeiroObjeto.equals(segundoObjeto); este teste, quando não sobrescrito apenas testa se os 2 objetos são referencia de uma mesma instancia, ou seja, tem o mesmo efeito que primeiroObjeto == segundoObjeto;.

Antes de implementar um equals em nossos objetos é preciso saber de cinco regras.
Equals...
  1. É reflexivo: para qualquer qualquer referencia não nula a, a.equals(a) tem sempre que ser verdadeiro, ou seja, um objeto é sempre igual a se mesmo.

  2. É simétrico: para quaisquer referencias não nulas de a e b, a.equals(b) so poderá retornar verdade se b.equals(a) retornar verdade, o mesmo raciocinio vale para falso.

  3. É transitivo: para quisquer referencias não nulas de a,b e c, se a.equals(b) e b.equals(c) são verdade então também tem que ser verdade que a.equals(c).

  4. É consistente - para quaisquer referencias não nulas de a e b, multiplas invocações de a.equals(b) terá sempre que retornar true ou sempre retornar false, enquanto informações usadas na comparação do equals não sejam alteradas.

  5. O resultado de equals() entre um objeto e nulo deve retornar sempre falso, a.equals(null) é sempre falso.


Com essa ideia podemos pensar em como implementar o equals para nossa classe Pessoa, sabemos que, duas Pessoas de mesmo nome podem ser diferente, portanto esse não pode ser nosso parametro para teste. Duas pessoas não podem ter o mesmo número de CPF, então este número é unico, e pode identificar uma pessoa, sendo assim nosso teste de igualdade entre duas pessoa pode ser

  1. @Override
  2. public boolean equals(Object obj) {
  3.     if (obj == null) {
  4.         return false; //nenhum objeto pode ser igual a null
  5.     }
  6.     if (!obj instanceof Pessoa) { //uma pessoa só pode ser igual a outra pessoa
  7.         return false;
  8.     }
  9.     final Pessoa other = (Pessoa) obj;
  10.     if (getCpf() == null) //se não houver CPF não há como testar as duas pessoas.
  11.         return false;
  12.     return getCpf().equals(other.getCpf()); //duas pessoas serão iguais...
  13.                                             //se seus CPFs forem iguais...
  14. }


HashCode:

Assim como há um teste de igualdade, Java ainda dispoem de outro teste, onde é possivel rapidamente, através de integer, restringir o campo de igualdade, através de hashCode(). assim como equals, o hashCode segue um contrato descrito abaixo:


  1. É constante: A qualquer momento durante a excecução de um programa java, o método hashCode deve constantemente retornar o mesmo inteiro, enquanto informações usadas na comparação do equals não sejam alteradas. O hashCode não precisa ser constante de uma execução da aplicação para outra.

  2. Se dois objetos a e b, são iguais, a.equals(b) retorna verdadeiro, então os inteiros produzidos por a e b devem ser os mesmos, ou seja a.hashCode() == b.hashCode()

  3. O inverso não é necessariamente verdadeiro, dois objetos que produzirem o mesmo hashCode podem ser diferentes



Diante disso a utilização do hashCode é principalmente para excluir igualdade, ou seja, ele serve como uma peneira, e é largamente utilizado por Collection em buscas como contains, remove, removeAll, retainAll entre outras coisas. A ideia é se os hashCode são diferentes então não é preciso testar a igualdade, pois já se garante que os objetos não serão iguais, poupando processamento no momento da busca, pois hashCode usa um calculo com int, que consome pouco processo, geralmente bem menos que o equals.

Existem varias estrategias para calculo do hashCode, desde a mais simples, onde vc praticamente exclui a funcionalidade do hashCode, deixando seu valor constante para qualquer objeto.

  1. @Override
  2. public int hashCode() {
  3.     return 0;
  4. }


Este método acima, funciona e respeita as regras de contrato de hashCode, porem seu programa poderá perder em velocidade. É extremamente desaconselhavel o uso de hashCode constante para qualquer objeto, pois várias funcionalidade que otimizam processos de busca não terão serventia com um hashCode mau projetado.

A melhor estratégia para hashCode é olhar para equals, e verificar como a igualdade é exegida, e assim montar um hashCode, concetenando os hashCode dos objetos que fazem a igualdade e designando um peso para as propriedades.

Por exemplo, para nossa classe Pessoa uma boa estrategia de hashCode seria

  1. @Override
  2. public int hashCode() {
  3.     int hash = 7;
  4.     hash = 23 * hash + (getCpf() != null ? getCpf().hashCode() : 0);
  5.     return hash;
  6. }


O número 7 contido nesse hashCode é apenas um número randomico para startar o hashCode, e diferenciar de outros objetos, por exemplo, um objeto empresa poderia iniciar por 3. o número 23 é outro número randomico, usado para quando há varias propriedades e escalar o hashCode entre as varias propriedade que definem um objeto igual.

Como no exemplo acima usamos o hashCode da string cpf, e como String tem seu hashCode bem implementando, podemos assim garantir que sempre que os CPF forem iguais os hashCode serão iguais.


Conclusão:

A igualdade entre objetos é uma parte fundamental da programação, e é uma das necessidades basica de logica, saber se dois objetos são iguais.

A implementação destes dois métodos é importante, e deve ser implementado em todas as classes instanciáveis do projeto, de forma a prover mecanismos corretos de comparação de igualdade (equals), assim como uma forma de otimizar os processos de busca (hashCode).

Muitos programadores só começam a entender a real necessidade da correta implementação destes dois métodos ao utilizar mais largamente a Java Collection Framework do pacote java.util, pois varios de seus métodos como contains, removeAll, remove, ratainAll ... falham quando equals não esta corretamente implementado, e perdem perfomance ou falham quando hashCode não esta otimizado ou corretamente implementando.



CollectionUtils e Filter<T> Manipulando e criando buscas.

A algum tempo atrás eu andava com uma dúvida de como realizar buscas genéricas em uma Collection tipada, ate que um colega Sérgio Taborda me sugeriu o uso de uma interface muito simples.

A ideia é muito simples cria-se uma interface chamada Filter<T> que é um contrato, que relata como deve ser o objeto que queremos encontrar, e então cria-se rotinas simples baseando neste contrato (a interface) para buscas.

interface Filter<T>

  1. /**
  2.  * Filtro de objeto, que testa se um obejto candidato T confere com o filtro.
  3.  * @author Tomaz Lavieri
  4.  * @param <T> o tipo de objetos que o filtro testa.
  5.  * @see CollectionUtils
  6.  */
  7. public interface Filter<T> {
  8.     /**
  9.      * Verifica se o objeto candidato passa pelo filtro.
  10.      * @param candidate Objeto candidato.
  11.      * @return  <tt>true</tt> - caso o candidato passe no filtro.
  12.      *          <br><tt>false</tt> - caso o candidato não pesse pelo filtro.
  13.      */
  14.     public boolean match(T candidate);
  15. }


É com essa ideia do filtro, que se cria um critério para buscas em coleções, por exemplos, vamo supor que agente tem uma classe chamada Pessoa e que queremos buscar todas as pessoas que contenham o nome "João" e que a idade seja maior ou igual a 18 anos.

  1. Filter<Pessoa> joaoMaioresDe18 = new Filter<Pessoa>() {
  2.     public boolean match(Pessoa candidate) {
  3.         boolean nomeOk = candidate.getNome() != null ?
  4.             candidate.getNome().toLowerCase().contains("joão") :
  5.             false;
  6.  
  7.         return nomeOk && candidate.getIdade() >= 18;
  8.     }
  9. };


Agora temos um critério de busca que foi guardado na variável joaoMaioresDe18 e podemos checar facilmente se qualquer pessoa passa pelo criterio utilizando joaoMaioresDe18.match(pessoa); este retorna true caso a pessoa seja um joão maior de 18 anos.

Agora podemos criar uma rotina para buscar em uma lista, todas as pessoas que pertençam ao criterio informado, ficando assim:

  1. List<Pessoa> pessoas = getAllPessoas(); //abstraindo como essa lista é obtida.
  2. List<Pessoa> resultado = new ArrayList<Pessoa>(0); //guarda o resultado
  3. for (Pessoa pessoa : pessoas) //realizando a busca
  4.     if (joaoMaioresDe18.match(pessoa))
  5.         resultado.add(pessoa);


Após isto, temos em resultado, todas as pessoas que contém o nome "joão" e que são maiores de idade.

Ficou bom, mais o processo pode ser automatizado e tornar-se ainda melhor, para isso é criado uma classe utilitária chamada CollectionUtils onde podemos guardar rotinas como:
  1. Buscar todos os resultados que coincidem com o filtro - findAllMatch
  2. Buscar todos os resultados que não coincidem com o filtro - findAllNotMatch
  3. Buscar o primeiro resultado que coincidir com o filtro - findFirstMatch
  4. Buscar o primeiro resultado que não coincir com o filtro - findFirstNotMatch
  5. Reter na lista total apenas os objetos que coincidirem com o filtro - retainAll
  6. Remover da lista total todos os objetos que coincidirem com o filtro - removeAll
  7. Adciona a uma segunda lista todos os itens da primeira lista que coincidirem com o filtro - addAllMatch
  8. Adciona a uma segunda lista todos os itens da primeira lista que não coincidirem com o filtro - addAllNotMatch


Colocarei aqui apenas um trecho de CollectionUtils e o link para o código completo segue aqui http://pastebin.com/f9d80cb7

class CollectionUtils
  1. import java.util.ArrayList;
  2. import java.util.Collection;
  3. import java.util.Iterator;
  4. import java.util.List;
  5.  
  6. /**
  7.  * Classe utilitária para manipular coleções
  8.  * @author Tomaz Lavieri
  9.  */
  10. public class CollectionUtils {
  11.     /**
  12.      * Não é possivel instaciar CollectionUtils.
  13.      */
  14.     private CollectionUtils(){}
  15.  
  16.     /**
  17.      * Busca todos os objetos <b>T</b> na lista de <tt>candidates</tt> que
  18.      * conferem com o <tt>filter</tt>.
  19.      * <BR>
  20.      * <BR>Tem o resultado inverso a {@link #findAllNotMatch(Collection, Filter)}
  21.      * @param   <T> o tipo de objeto a ser buscado.
  22.      * @param   candidates a coleção de condidatos aonde deseja-se realizar a
  23.      *          busca.
  24.      * @param   filter o filtro que será usado para <b><u>aceitar</u></b> um
  25.      *          candidato no resultado da busca.
  26.      * @return  todos os itens que passaram pelo filtro.
  27.      * @see #findAllNotMatch(Collection, Filter)
  28.      */
  29.     public static <T> List<T> findAllMatch(Collection<? extends T> candidates,
  30.             Filter<T> filter) {
  31.         List<T> matchs = new ArrayList<T>(0);
  32.         addAllMatch(candidates, filter, matchs);
  33.         return matchs;
  34.     }
  35.  
  36.     /**
  37.      * Adciona a coleção <tt>recipient</tt> todos os objetos <tt>T</tt> da lista
  38.      * de <tt>candidates</tt> que coincidirem com o <tt>filter</tt>.
  39.      * <BR>
  40.      * <BR>Adciona o inverso dos itens de <tt>candidates</tt> que
  41.      * {@link #addAllNotMatch(Collection, Filter, Collection)} adcionaria.
  42.      * @param   <T> o tipo de objeto a ser adcionado.
  43.      * @param   candidates a coleção de candidatos a serem adcionados ao
  44.      *          <tt>recipient</tt>.
  45.      * @param   filter o filtro que será usado para <b><u>aceitar</u></b> um
  46.      *          candidato a ser adcionado ao <tt>recipient</tt>.
  47.      * @param   recipient coleção onde os objetos serão adicionados.
  48.      * @see #addAllNotMatch(Collection, Filter, Collection)
  49.      */
  50.     public static <T> void addAllMatch(Collection<? extends T> candidates,
  51.             Filter<T> filter, Collection recipient) {
  52.         for (T object : candidates)
  53.             if (filter.match(object))
  54.                 recipient.add(object);
  55.     }
  56.    
  57. //... restante da classe segue no link indicado
  58.  
  59. }


Agora com essas duas classes, basta criar um filtro, e mandar junto com a coleção, para CollectionUtils e receber os resultados.

Desta forma espera-se aumentar a produtividade, e facilitar a manipulação de coleções.



Filter.java
CollectionUtils.java


Seguidores