Brincando com Generics: o BizarreGenericDao

Por Paulo Silveira em 29/10/06

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.

8 Comments »

  1. 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 :)

    Comment by Orseni Ferreira Campos — October 30, 2006 @ 12:47 pm

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

    Comment by Ricardo — October 30, 2006 @ 12:47 pm

  3. { ops, abre e fecha chaves }

    Comment by Ricardo — October 30, 2006 @ 12:48 pm

  4. Realmente muito boa a gam bom o ajuste fino!

    Comment by Fabio Kung — October 30, 2006 @ 2:43 pm

  5. 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.

    Comment by Alexandro — July 12, 2007 @ 8:56 am

  6. 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.

    Comment by Edson Watanabe — April 14, 2008 @ 9:18 pm

  7. 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!).

    Comment by Paulo Silveira — April 15, 2008 @ 7:10 pm

  8. 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

    Comment by ronildo braga — May 8, 2008 @ 5:14 pm

RSS feed for comments on this post. TrackBack URL

Leave a comment