Este tutorial mostra como utilizar Generics em Java para nos auxiliar na criação de classes robustas e com poder de abstração imenso, ou seja, você será capaz de criar uma classe poderosa o bastante para comportar-se das mais diversas formas possíveis dependendo da situação que você precisar, através do conceito de reusabilidade.

É muito comum em qualquer sistema o uso de operações de CRUD (Inserção, Deleção, Remoção e Pesquisa), mas em certo ponto isso torna um código repetitivo e a produtividade de desenvolvimento do sistema cai muito. Tais problemas poderiam ser solucionados apenas criando uma classe Abstrata com Generics que faça todas as operações considerando o tipo do bean em questão.

Criando uma Classe Crud com Generics

Vamos começar utilizando o poder do Generics com uma classe de CRUD, ou seja, que tenha todas as operações de manutenção a determinado objeto. Nosso objetivo é deixar a nossa classe o mais complexa (em termos de recurso e não de dificuldade) possível para atender as mais diversas possibilidades, sendo assim, apenas em casos muito específicos teremos que criar classes a parte, ou seja, que não estendam nossa classe abstrata. Observe a Listagem 1.


    import java.lang.reflect.ParameterizedType;
    import java.lang.reflect.Type;
    import java.util.List;
    
    /*
    * Nossa classe CrudImpl será responsável por todas as operações 
    * de CRUD do sistema, apenas em alguns casos onde alguma 
    * operação de CRUD Deverá ser mais especializada que os métodos 
    * deverão ser sobreescritos (override).
    * */
    public abstract class AbstractCrud<Bean> {
    
         /*
          * Nos permite retornar novas instancias do nosso objeto
          */
         protected Bean criadorBean;
         protected BasicDAOImpl basicDAO;
    
         public Bean save(Bean bean) {
          try {
           if (bean == null) {
            throw new RuntimeException("O Objeto não pode ser nulo");
           }
           // Se o seu ID for diferente de NULO quer dizer 
           // que ele já foi salvo apenas ignoramos um novo "save"
           if (bean.getId() != null) {
             return bean;
           }
           return (Bean) getDao().save(bean);
    
          } catch (Exception e) {
            e.printStackTrace();
            return null;
          }
    
       }
    
       public Bean update(Bean bean) {
        try {
         if (bean == null) {
           throw new RuntimeException("O Objeto não pode ser nulo");
         }
         // Se o seu ID for NULO, chamamos o save em vez do update
         if (bean.getId() == null) {
          return save(bean);
         }
         return (Bean) getDao().update(bean);
        } catch (Exception e) {
          e.printStackTrace();
          return null;
        }
      }
    
      public void remove(Bean bean) {
        try {
         if (bean == null) {
           throw new RuntimeException("O Objeto não pode ser nulo");
         }
         // Se o seu ID for NULO, significa que o objeto não foi salvo, 
         // então não podemos remove-lo
         if (bean.getId() == null) {
           throw new RuntimeException(
             "Você não pode remover um objeto que ainda não foi salvo");
         }
    
           getDao().remove(bean);
        } catch (Exception e) {
           
           e.printStackTrace();
        }
      }
      public List<Bean> findAll(Bean bean) {
        try {
          if (bean == null) {
           throw new RuntimeException("O Objeto não pode ser nulo");
          }
          return (List<Bean>) getDao().findAll(bean);
        } catch (Exception e) {
          e.printStackTrace();
          return null;
        }
    
      }
    
      /*
       * Retorna uma instancia do Bean, ou seja, como se estivessemos 
       * executando o"new MinhaClass();", mas faremos isso com Generics.
       */
      public Bean getInstanceOfbean() {
       Type type = getClass().getGenericSuperclass();
       ParameterizedType paramType = (ParameterizedType) type;
       try {
        return ((Class<Bean>) paramType.getActualTypeArguments()
        [0]).newInstance();
       } catch (InstantiationException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
         return null;
       } catch (IllegalAccessException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
         return null;
       }
     }
    
     public BasicDAOImpl getDao() {
       if (basicDAO == null)
         basicDAO = new BasicDAOImpl();
       return basicDAO;
      
     }
    
    }
Listagem 1. Classe Abstrata implementando operações de CRUD

O importante na Listagem 1 é perceber que podemos usar dos mais diversos recursos para tornar nossa classe abstrata e poderosa o suficiente para que adapte-se ao meio, ou seja, dependendo da regra em que estamos trabalhando, nossa classe será capaz de adaptar-se a isto, evitando a reescrita de código.

É verdade que a construção dessa classe pode levar um pouco de tempo e ser demasiadamente trabalhosa, mas vale ressaltar que a criação dessas classes abstratas poderosas, aumentam a produtividade do desenvolvimento de forma escalar, levando ainda em consideração que você pode reutilizar a mesma classe nos mais diversos sistema, ou seja, o trabalho árduo será feito apenas uma vez.

Temos então nossa classe abstrata criada, e pelo fato de ser abstrata, não pode ser implementada. Precisamos então criar uma classe que a implemente.

Para nosso exemplo criaremos a classe PessoaCrudImpl (Listagem 2) que estenderá nossa classe abstrata implementando os métodos necessários e abstraindo todos os recursos contidos na mesma.


    public class PessoaCrudImpl extends AbstractCrud<Pessoa> {   
    }
Listagem 2. PessoaCrudImpl estendendo da classe AbstractCrud

Nossa classe PessoaCrudImpl tem recurso suficiente para realizar todas as operações de CRUD necessárias, pois através do “AbstractCrud<Pessoa>” dizemos a nossa classe abstrata que o tipo de objeto que estamos trabalhando é do tipo Pessoa e este tipo é atribuído diretamente ao parâmetro “Bean” especificado no “AbstractCrud<Bean>” da nossa classe abstrata.

Veja o quão simples se tornar trabalhar desta forma, evitando trabalho desnecessário. É óbvio que este é um exemplo bem simples do uso de Generics para reusabilidade de código. Você pode usar muito mais recursos do que os mostrados neste artigo, pense na sua lógica de negócio e tente resolver o máximo possível apenas com uma classe Abstrata, assim você evita retrabalho e tem mais tempo para término do projeto.

Podemos, por exemplo, criar uma classe utilizando um recurso muito interessante do Generic que é a criação de uma instância apenas através do seu tipo, como é o caso do nosso método “getInstanceofBean()” que quase que “milagrosamente” cria uma nova instância do nosso objeto apenas conhecendo seu tipo que foi passado pelo “PessoaCrudImpl<Pessoa>”. Na Listagem 3 fazemos uso deste método aplicando uma lógica de negócio própria, apenas para você entender que poderíamos aplicar diversas funcionalidades para este método.


    public class PessoaCrudImpl extends AbstractCrud<Pessoa> {
       
       
       public Pessoa prepararInsercaoDePessoa(){
             Pessoa pessoa = getInstanceOfbean();
             pessoa.setPessoaFisica(new PessoaFisica());
             pessoa.setContato(new Contato());
             
             pessoa.setNumerador(criarNumeracaoDePessoa());
             
             if (pessoa.getNumerador > 100){
                    pessoa.setNivel(pessoa.getNumerador() / 5);
             }
             
             return pessoa;
       }
     
    }
Listagem 3. Aumentando os recursos de PessoaCrudImpl

Veja na Listagem 4 uma alternativa a instanciação de objetos usando generics.


    package org.foo.com;
     
    import java.lang.reflect.ParameterizedType;
    import java.lang.reflect.Type;
     
    /**
     * Basically the same answer as noah's.
     */
    public class Home<E>
    {
     
        @SuppressWarnings ("unchecked")
        public Class<E> getTypeParameterClass()
        {
            Type type = getClass().getGenericSuperclass();
            ParameterizedType paramType = (ParameterizedType) type;
            return (Class<E>) paramType.getActualTypeArguments()[0];
        }
     
        private static class StringHome extends Home<String>
        {
        }
     
        private static class StringBuilderHome extends Home<StringBuilder>
        {
        }
     
        private static class StringBufferHome extends Home<StringBuffer>
        {
        }   
     
        /**
         * This prints "String", "StringBuilder" and "StringBuffer"
         */
        public static void main(String[] args) 
         throws InstantiationException, IllegalAccessException
        {
            Object object0 = new 
             StringHome().getTypeParameterClass().newInstance();
            Object object1 = new
             StringBuilderHome().getTypeParameterClass().newInstance();
            Object object2 = new 
             StringBufferHome().getTypeParameterClass().newInstance();
            System.out.println(object0.getClass().getSimpleName());
            System.out.println(object1.getClass().getSimpleName());
            System.out.println(object2.getClass().getSimpleName());
        }
     
    }
Listagem 4. Alternativa para instanciar objetos com generic

No exemplo da Listagem 4 não retornamos o objeto instanciado mas sim uma classe para então podermos instanciar o objeto em outro local, no nosso caso, utilizamos o método “main” para tal tarefa. Criamos três objetos distintos apenas com o uso de Generics.

Com isso, podemos concluir que o uso do Generics sem dúvida é um assunto vasto e muito poderoso. O objetivo deste artigo foi mostrar o uso de tal recurso aplicado a reusabilidade de código, dando foco a instanciação de objetos utilizando Generics.

Veja Também: