Brincando com Generics: o BizarreGenericDao

Conversando com o Orseni Campos, ele me contou de uma sacada muito interessante que teve juntamente com seu colega Alexandre Bitencourt para resolver um clássico problema do generics: em tempo de execução você não consegue descobrir o tipo parametrizado que foi passado como argumento.

Em outras palavras, repare no código do Dao genérico que foi discutido aqui (estou pulando a interface por questão de simplicidade):

public class Dao<T> {
  // ...
  public Dao(Session session, Class<T> persistentClass) {
    this.session = session;
    this.persistentClass = persistentClass;
  }
}

Aí, quando vamos criar um novo dao genérico:

Dao<Livro> dao = new Dao<Livro>(session, Livro.class);

Parece estranho ter de passar a Class que representa o livro, se eu já estou passando essa informação através do tipo parametrizado. Seria interessante fazer algo como:

public class Dao<T> {
  // ...
  public Dao(Session session) {
    this.session = session;
    this.persistentClass = T.class;
  }
}

Isso não compila, essa informação (sobre quem é T dentro de Dao) não é armazenada em um .class. O motivo é simples. Só existe uma classe Dao, não importa como ela foi instânciada. Em outras palavras:

(new Dao<Autor>(session, Autor.class)).getClass() == 
   (new Dao<Livro>(session, Livro.class)).getClass()

retorna true. Então o problema é o seguinte: existe apenas uma classe, não importa quantas instâncias de tipo parametrizado diferentes você criar. Não faria sentido algum ter um método como clazz.getTypeArguments() que te devolvesse uma array com os tipos parametrizados que tivessem sido usados na instanciação, já que só existe uma classe para todas as instâncias de diferentes tipos parametrizados. Isso se deve a erasure: em tempo de execução só sabemos que T pode ser um filha de Object, nesse caso.

Mas e se existisse mais de uma classe?

class AutorDao extends Dao<Autor> {
}

class LivroDao extends Dao<Livro> {
}

Obviamente (new LivroDao()).getClass() == (new AutorDao()).getClass() retorna false. Mais que isso, agora você consegue buscar por reflection qual é o parâmetro que foi passado na hora de estender a classe Dao, já que AutorDao e LivroDao possuem essa informação em seus bytecodes:

Class clazz = (Class<T>) ((ParameterizedType) 
getClass().getGenericSuperclass()).getActualTypeArguments()[0];

Então, se colocamos essa linha dentro da nossa classe mãe:

public class Dao<T> {
  // ...
  public Dao(Session session) {
    this.session = session;
    this.persistentClass = (Class<T>) ((ParameterizedType) 
      getClass().getGenericSuperclass()).getActualTypeArguments()[0];
  }
}

Agora funciona caso utilizemos uma instância de uma filha de Dao que tenha explicitado quem é T. Bem, eu não gostei dessa solução porque nos obrigava a ter uma classe filha, mesmo que vazia, para cada entidade. Mas o Orseni e o Alexandre não desistiram. A proposta deles foi de declarar Dao como abstrata, e na hora de instanciar:

Dao<Livro> dao = new Dao<Livro>(session){};

Repare o {}! Esse código vai gerar uma classe anônima em tempo de compilação, e essa classe sem nome vai ser filha de Dao<Livro>, e agora podemos pegar essa informação por reflection! Isso sim é gambiarra criatividade :). Usar em produção? Acho que não seria elegante. Nem eles estão usando, mas foi um bonito desafio.

15 Comentários

  1. Orseni Ferreira Campos 30/10/2006 at 12:47 #

    Muito bom seu artigo Paulo, otimos exemplos…
    Foi mesmo uns testes que o Alexandre e eu fizemos aqui e no final rendeu umas risadas 🙂
    Agora pra ir mais longe na viagem na maionese, imagina usar o CGLIB pra criar as classes anonimas de forma transparente
    Gambi… ops… criativadade não falta 🙂

  2. Ricardo 30/10/2006 at 12:47 #

    Gambiarra digna de expert 🙂 Eu como newba apagaria todos esses abre e fecha parenteses e geraria trabalho pra mais alguns meses pra empresa hahaha

  3. Ricardo 30/10/2006 at 12:48 #

    { ops, abre e fecha chaves }

  4. Fabio Kung 30/10/2006 at 14:43 #

    Realmente muito boa a gam bom o ajuste fino!

  5. Alexandro 12/07/2007 at 08:56 #

    Muito boa esta solução.
    Estava “batendo” a cabeça a algum tempo para melhorar algumas classes genéricas em que eu tinha que passar o Class e isso resolveu o meu problema.

  6. Edson Watanabe 14/04/2008 at 21:18 #

    Isso dá o seguinte problema:

    Dao dao = new Dao(session){};
    Dao dao2 = new Dao(session){};

    Cada vez que se declara uma nova variável, cria-se uma nova classe anônima. Se você declarar 10 variáveis, vai ter 10 classes diferentes. Outra coisa chata é que:

    dao.getClass().equals (dao2.getClass()) retorna false, o que é meio estranho se você simplesmente olhar a declaração.

  7. Paulo Silveira 15/04/2008 at 19:10 #

    Edson, mas qual é o problema dele gerar mais uma classe interna? E sobre nao ser a mesma classe, também não vejo problema.

    De qualquer maneira, esse post aqui foi para mostrar como a linguagem é complicada, e não aconselho o uso de um DAO tão confuso assim (e isto está explícito no final do post!).

  8. ronildo braga 08/05/2008 at 17:14 #

    Acho que fica mais elegante desta forma abaixo, e não me parece gambiarra:
    public class GenericDAO implements GenericRepository{

    protected EntityManager em;

    public DAOImpl setEntityManager(EntityManager em){
    this.em = em;
    return (DAOImpl)this;
    }

    public void persist(T entity){
    try{
    em.persist(entity);
    }catch(PersistenceException e){
    logger.severe(CANNOT_PERSIST);
    throw new RepositoryException(e);
    }
    }
    }

    E para instanciar vc faz o seguinte:
    new LoginDAO().setEntityManager(entityManager);

    Eu também omiti a maior parte do código por questão de simplicidade, mas vc pode visualizar o restante do código no meu blog

  9. Flavio 20/06/2009 at 15:40 #

    Como não dava p criar quase 100 classes anonimas na mão no projeto que trabalho, tive que aumentar um pouco a “inovação”.
    Class clazz = (Class) ((ParameterizedType)
    getClass().asSubclass(getClass()).getGenericSuperclass()).getActualTypeArguments()[0];

  10. Salvio 21/03/2013 at 19:12 #

    O fato de eu estar instanciando a classe como uma classe anonima interfere no uso da memória? ou algo assim?

    Gostei da solução, eu já tinha achado esse método, mas não tava entendendo porque eu não conseguia obter o tipo criando em modo de execução, obrigado pela explicação.

    vou usar isso no meu projeto, a nao ser que eu descubra que isso vai me dar algum preju (aí vai ser só mudar os constructors).

    Obrigado pelo post!

  11. Tiago 17/05/2016 at 20:36 #

    Eu vejo sempre um dizendo que uma coisa ou outra é gambiarra, mas velho amigão:
    Não existe gambiarra, cada um tem uma forma, isso é orientação a objetos.
    exemplo:
    3+5+2 = 10
    é a mesma coisa que
    2+3+5 = 10
    Existem padrões de projeto, porém não existem regras para se chegar a um objetivo, no meu caso o número “10”.
    Muitas pessoas dizem: RXTX.jar é gambiarra? Claro que não! Eu escrevi um aplicativo completo para impressoras fiscais, leitores de código de barra, balança, e muitas outras coisas usando rxtx, tudo fácil! Tudo retornando certinho. Gabiarra é o J# da microsoft! Muitos dizem: Vou partir pro C#. C/C++, C#, J#, ASPX, tudo isso a microsoft fez de inveja do PHP e do JAVA. Me diz pra que a Microsoft tem tanta linguagem de programação? Com Java eu faço tudo que estas linguagens fazem! Apenas uma linguagem e não 5, 10, 20, igual a microsoft quer implantar no universo da programação!

  12. Tiago 17/05/2016 at 20:52 #

    Não tenha exageros em preocupação com memória, se você não se preocupar em optimizar tarefas! Se você quer o nome do usuário, então não use o “* from”. Se for um Aplicativo para smartphone, faça um DB local no aparelho para evitar consultas online desnecessárias. Cookies podem ser inseguros as vezes, mas sabendo usar, podem evitar pesquisas genéricas DB. Tente na medida do possível fazer tarefas (que não comprometam em segurança) no frontend, pois hoje o mínimo que se tem é 4GB de ram nos desktops, por isso javascript é tão importante.

Deixe uma resposta