tags:

views:

242

answers:

4

Hi there: It seems I'm missing something with Java Generics because something I think is simple, it appears to me that can´t be done. Maybe you can help...

This is the scenario: I'm coding a generic abstract DAO with simple CRUD operation so every specific DAO of my application can have it for free:

public abstract DefaultDAO<T,V> {
  private EntityManager manager;

  public BaseDAO(EntityManager em) {
    this.manager = em;
  }

  public void create(T entity) {
    manager.persist(entity);
  }

  // You know the others...

  public T read(V pk) {
    // Now, here is the problem. 
    // EntityManager signature is: public <T> T find(Class<T> entityClass, Object primaryKey);
    // So I must provide the type of the object this method will be returning and 
    // the primary key.
    // resulting object will be T typed and pk type is V type (although is not needed to type it since the method expects an Object)
    // So... I expected to be allowed to do something like this
    return manager.find(T, pk); // But it's not allowed to use T here. T is not an instance
  }   
}

Now I would go and implement an specific DAO:

public PersonDAO extends DefaultDAO<PersonEntity, Long> {
  public PersonDAO(EntityManager em) {
    super(em);
  }
  // CRUD methods are inherited
}

And client code for my DAO would be:

EntityManager manager = ...
PersonDAO dao = new PersonDAO(manager);
Long pk = .....
PersonEntity person = dao.find(pk); // DAO would return a PersonEntity

When client executes code, BaseDAO knows the type of entity it must return and the type of the primary key of that entity because I set it on the specific dao, but I don't know how to code the read() method correctly.

Hope you can help. Thanks a lot!

+10  A: 

You're trying to use a type parameter as if it's a normal expression. You can't do this. In other languages you may be able to get the class for the type parameter at execution time, but you can't in Java due to type erasure.

In this case you'll need to pass in the Class<T> at execution time - for example to the constructor:

public abstract DefaultDAO<T,V> {
  private EntityManager manager;
  private final Class<T> clazz;

  public BaseDAO(EntityManager em, Class<T> clazz) {
    this.manager = em;
    this.clazz = clazz;
  }
  public T read(V pk) {
    return manager.find(clazz, pk);
  }   
}
Jon Skeet
This is however possible if Java is going to support the so-called reified generics. You could then use for instance `T.class` here.
BalusC
@BalusC: I'm going to wait until that's confirmed before starting to talk about it in answers. Java generics are confusing enough without bringing future possible features into the mix :)
Jon Skeet
Isn't this weird to name variables like 'clazz'? From where did this come?
Mykola Golubyev
@Mykola: You can't call it "class" because that's a keyword. It's the commonly-used alternative.
Jon Skeet
I know. Probably aClass, theClass? Why change the word itself. We don't name variable 'apple' as 'abble' if we already have 'apple' one. That is what I was wondering. Who decide to change 'ss' to 'zz'
Mykola Golubyev
I don't know, it's just a convention that's been created. I guess "ss" and "zz" look and sound similar - and personally I don't like "a" or "the" prefixes.
Jon Skeet
It's a convention started by Sun itself - if you look at methods that take parameters of type `Class` (e.g. those of `Class` itself) in JDK docs, you'll see that they use `clazz` for parameter names.
Pavel Minaev
+2  A: 

Java doesn't have the generic class information at runtime anymore (it is called Type Erasure). What I did was to give my Abstract Daos the instance of the generic class.

public abstract DefaultDAO<T,V> {

  protected Class<T> genericClass;
  private EntityManager manager;

  protected BaseDAO(EntityManager em, Class<T> implclass) {
    this.manager = em;
  }

  public void create(T entity) {
    manager.persist(entity);
  }

  // You know the others...

  public T read(V pk) {
    return manager.find(this.getGenericClass(), pk); // But it's not allowed to use T here. T is not an instance
  }

  public Class<T> getGenericClass()
  {
    return genericClass;
  }
}

public class Blubb
{
    private String id;

    // Getters and Stuff...
}

public class BlubbDao extends DefaultDAO<Blubb, String>
{
    public BlubbDao(EntityManager em)
    {
     super(em, Blubb.class);
    }
}

I can't promise that it will run out of the box but I hope you get the idea.

Daff
A: 

This article does what you need it to do. http://blog.xebia.com/2009/03/09/jpa-implementation-patterns-data-access-objects/

Drew
+1  A: 

There is a way to do this using reflection, as long as your classes follow a consistent class hierarchy in terms of generics (i.e. any intermediate classes in the inheritance hierarchy between your base class and your concrete class use the same generic parameters in the same order).

We use something like this in our abstract class, defined as HibernateDAO where T is the entity type and K is the PK:

private Class getBeanClass() {
    Type daoType = getClass().getGenericSuperclass();
    Type[] params = ((ParameterizedType) daoType).getActualTypeArguments();
    return (Class) params[0];
}

which smells a bit but trades off the ickiness of passing a .class up from the concrete implementation in a constructor for the ickiness of insisting that you keep your type hierarchy consistent in that way.

Cowan