tags:

views:

211

answers:

2

I am trying to write a Generic Data Access Layer for my application. I have multiples hibernate entities which are mostly the same and are represented as a class hierarchy in Java (they are not implemented as a hierarchy in Hibernate) :

public abstract class Entity1 {
    // some implementation
}
public class Entity2 extends Entity1 {
    // some implementation
}
public class Entity3 extends Entity1 {
    // some implementation
}

The DAOs for those entities are mostly the same (except for the type signature of the methods, and the class asked to Hibernate). I would like to be able to write a generic DAO like this :

public interface EntityDao<T extends Entity1> {
    List<T>getEntities();
    void saveOrUpdateEntity(T entity);
}

public class EntityDaoImpl implements EntityDao<Entity1> {
    private final HibernateTemplate hibernateTemplate;

    private final Class<DBEnumerationDto> clazz;

    public DBEnumerationDaoHibernateImpl(SessionFactory sessionFactory, Class<DBEnumerationDto> clazz) {
        this.hibernateTemplate = new HibernateTemplate(sessionFactory);
        this.clazz = clazz;
    }

    @Override
    public List<Entity1> getEntities() {
        return this.hibernateTemplate.loadAll(this.clazz);
    }
    @Override
    public void saveOrUpdateEntity(Entity1 entity) {
        this.hibernateTemplate.saveOrUpdate(entity);
    }
}

So far, so good. But the problem arises when using this :

Entity1 test = new Entity1();
Entity1Dao<? extends Entity1> dao = ...; // get the dao forthe current operation
dao.saveOrUpdate(test);

This gives a compiler error : The method saveOrUpdateEntity(capture#5-of ? extends Entity1) in the type EntityDao<capture#5-of ? extends Entity1> is not applicable for the arguments (Entity1)

I guess this question relates to http://stackoverflow.com/questions/1375718/java-generics-casting-to-or-a-way-to-use-an-arbitrary-foo but I cant really grasp in which way.

How should I fix my code ? Or is my approach wrong ?

A: 

You shouldn't give the extends keyword in the actual instantiation of a generic class because it needs to know what type it actually is. You will have to use that type or a wildcard, meaning either

Entity1Dao<Entity1>
// or
Entity1Dao<?>

If you are using Hibernate and want to implement a generic DAO you probably won't need the Entity superclass at all, because the Hibernate Template can work with any class that is a registered entity. I tried the Entity Superclass approach once, too but it just became a huge generic hassle without much benefit (especially when using Hibernate annotations, which don't play very well with inheritance).

Daff
+1  A: 

Think for a second about what the question mark (wildcard) means in the second line of your failing example.

You have obtained an Entity1Dao, with an unknown generic parameter (the only thing that it known is that the parameter is a subclass of Entity1). So it would be perfectly legal for this to actually be implemented as follows:

Entity1Dao<? extends Entity1> dao = getEntityDao();

private Entity1Dao<Entity2> getEntityDao()
{
    return new Entity1Dao<Entity2>(); // Or whatever (the construction itself is irrelevant)
}

Because of the wildcard, assigning an Entity1Dao<Entity2> is perfectly legal. Now you go onto your next line and try to call dao.saveOrUpdate(), passing in an object of type Entity1.

This cannot work - as we've just shown above the dao is parameterised on Entity2 and so only has the concrete method saveOrUpdate(Entity2 entity)! Hence why the compiler is giving you type warnings.


In summary, your problem is the "extends" keyword, which allows the wildcard parameter to be a subclass of your actual type and thus unable to handle it. If you change the wildcard to use super (i.e. <? super Entity1>) instead, this will compile as the compiler can be sure that regardless of the actual generic type, the saveOrUpdate method will accept an argument of type Entity1.

Often it turns out that you need the same parameter to be both super and extends compared to some exact type, which means that you cannot use wildcards at all. In this case, you may wish to make the whole method generified on the concrete type of the class, something like this:

public <T extends Entity1> void saveEntityExample(T test)
{
   Entity1Dao<T> dao = getEntityDao();
   dao.saveOrUpdate(test);
}

private <T extends Entity1> Entity1Dao<T> getEntityDao()
{
   // Get the DAO however
}

You may also want to check out Josh Bloch's talk as linked in this answer, in particular to pick up on the concept of "PECS". Unlike classes where "extends" is always the answer, one needs to think about whether they mean "extends" or "super" when it comes to generics and the mnemonic is a useful way of remembering the general rules.

Andrzej Doyle