views:

476

answers:

2

In searching for an answer to an interesting situation which I had recently encountered I came upon the following question: Type safety, Java generics and querying

I have written the following class (cleaned up a bit)

public abstract class BaseDaoImpl<T extends Serializable> extends HibernateDaoSupport implements BaseDao<T> {

    /**
     * Finds and Returns a list of persistent objects by a collection of criterions
     * @param criterions
     * @return list of persistent objects
     * @throws DBException
     */
    @SuppressWarnings("unchecked")
    protected List<T> findByCriteria(Collection<Criterion> criterions) throws DBException {
     try {
      DetachedCriteria criteria = DetachedCriteria.forClass(T.class); // BAD!!!
      for (Criterion criterion : criterions) {
       criteria.add(criterion);
      }

      List<T> result = getHibernateTemplate().findByCriteria(criteria);
      return result;
     }
     catch (Exception e) {
      throw new DBException(T.class + " lookup by " + criterions + " failed", e); // BAD!!!
     }
    }
}

For some (probably good reason) T.class causes a compile time error.

My first question is why?

If I change it to T.getClass() which obviously shouldn't compile - because no 'T' when "expanded" or goes through "erasure" - should have a static method such as that. The eclipse IDE gives the following compilation message:

Cannot make a static reference to the non-static method getClass() from the type Object

My second question is why? And what does this error imply actually?

Finally, would solving this in the manner specified in the link above (or rather my interpretation of) be the most optimal way?

public abstract class BaseDaoImpl<T extends Serializable> extends HibernateDaoSupport implements BaseDao<T>, MyGenericHelper<T> {

    /**
     * Finds and Returns a list of persistent objects by a collection of criterions
     * @param criterions
     * @return list of persistent objects
     * @throws DBException
     */
    @SuppressWarnings("unchecked")
    protected List<T> findByCriteria(Collection<MyCriterion> criterions) throws DBException {
     try {
      DetachedCriteria criteria = DetachedCriteria.forClass(getGenericClass()); // BAD!!!
      for (Criterion criterion : criterions) {
       criteria.add(criterion);
      }

      List<T> result = getHibernateTemplate().findByCriteria(criteria);
      return result;
     }
     catch (Exception e) {
      throw new DBException(getGenericClass() + " lookup by " + criterions + " failed", e); // BAD!!!
     }
    }
}

public interface MyGenericHelper<T extends Serializable>  {
    public Class<T> getGenericClass();
}

Thanks!

+5  A: 

The reason why T.class can't be accessed is because T is erased at compile time, so it doesn't exist at runtime to get a class.

The typical hack around this is to make a factory method:

 public static <T extends Serializable> BaseDAOImpl<T> createBaseDAO(Class<T> klass) {
       return new BaseDAOImpl<T>(klass);
 }

And then in the constructor store the klass variable as a field and reference it when you need it.

You could use your interface (personally I would just go with a protected abstract method) if you want to keep a no-arg constructor.

 protected abstract Class<T> getGenericClass();

EDIT: In the case of the generic abstract superclass, there are a couple of options. One is have the constructor and then have the subclasses just have to call it (by not having a no-arg constructor). Something like this:

  protected BaseDAOImpl(Class<T> klass) {
       //store the parameter in a field.
  }

Then the static method isn't relevant, as you have to create the subclass. The static factory method is more used when based on the class you can return the right implementation (so you have a factory, not just a strategy).

For the sake of completeness, I should point out that if the subclasses declare the generic when they extend the abstract class like this:

 public IntegerImpl extends BaseDAOImpl<Integer> {}

then the generic type is preserved in the class. There is a really ugly hack to get at this using the class. I experimented with this, and it worked:

  (Class<?>) ((ParameterizedType)  this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]

But it makes huge assumptions about the inheritance hierarchy, and should not be used in anything serious if it can at all be avoided, but I'm including it for the sake of a complete picture of what is going on.

Yishai
As for your first suggestion, in my example BaseDAOImpl is an abstract class, I can't therefore write a statement such as "new BaseDAOImpl<T>(klass)". In that respect what should I do and where should I place the factory method?
Yaneeve
+1  A: 

For some (probably good reason) T.class causes a compile time error.

My first question is why?

At compile time, compiler does not know the type T (in this case, it can be Integer, String), T.class can't be supported to return the actual class.

However, at the runtime, the type information is removed due to type erasure

Cannot make a static reference to the non-static method getClass() from

the type Object

My second question is why? And what does this error imply actually?

Hm.. getClass() is class (non-static) member method, which require an object. T is not an object, it is a type, so it fails. They don't make it static because there is already a XXX.class keyword from which you can get the Class object.

Oscar Chan
As for your second answer, well I know that, but why would I get such a remark if the type is unknown and can't be referenced?
Yaneeve
Well, it depends on how smart the compiler treats this error. I am guessing compiler is first looking the object from which the getClass() is called. In this case, the object is absent, so it assumes it is trying to make static method calls, which does not require an object. At a result, it says your code is making to make a "static" reference calls which the method is non-static.I know it is not clear, but it is much better than C++ template (i.e. generic) compile error messages.
Oscar Chan