Tuesday, August 01, 2006

Java generics trick to work out parameter types

Another nifty trick I hadn't seen before which I ran across on the Hibernate page for their generic DAO classes:

public abstract class GenericHibernateDAO<T, ID extends Serializable>
        implements GenericDAO<T, ID> {
    private Class<T> persistentClass;
    private Session session;

    public GenericHibernateDAO() {
        this.persistentClass = (Class<T>)
                ((ParameterizedType) getClass()
                .getGenericSuperclass())
                .getActualTypeArguments()[0];
    }

    // ...
}

Playing around with this revealed two things to me:

  1. This trick only works for concrete subclasses.
  2. The concrete subclass needs to not be generic, at least for the parameter in the interesting expression above.

So one could, for example, create a generic factory using this technique, although there does not seem on the surface to be much advantage:

public abstract class FactoryBase<T> {
    private final Class<T> type = (Class)
            ((ParameterizedType) getClass()
            .getGenericSuperclass())
            .getActualTypeArguments()[0];

    protected FactoryBase() { }

    public Class<T> getType() { return type; }

    public T create()
            throws IllegalAccessException, InstantiationException {
        // Real implementations do something more interesting
        // than imitate "new T()".
        return type.newInstance();
    }
}

public class FredFactory
        extends FactoryBase<Fred> { }

public class Main {
    public static void main(final String arguments) {
        final Fred fred = new FredFactory().create();
    }
}

How different is this from supplying a Class literal to the factory consturctor?

getGenericSuperclass() seems one of those "back pocket" techniques: something I'll keep handy if the need arises but which I don't go out of my way to use.

UPDATE: A little playing shows that the minimal requirement is that the generic parameter whose index is queried with getActualTypeArguments() must be concrete; it is ok for another parameter to be a generic:

public class Bob<T, U> {
    final Class<T> tClass
            = getGenericTypeByIndex(getClass(), 0);

    private static Class getGenericTypeByIndex(
            final Class clazz, final int index) {
        return (Class) ((ParameterizedType) clazz
                .getGenericSuperClass())
                .getActualTypeArguments()[index];
    }
}
}

This snippet works ok as:

public class Fred<U> extends Bob<Fred, U> {
    public static void main(final String arguments) {
        new Fred<String>();
    }
}

But fails at runtime with a ClassCastException trying to turn a sun.reflect.generics.reflectiveObjects.TypeVariableImpl into a java.lang.Class:

public class Sally<T, U> extends Bob<T, U> {
    public static void main(final String arguments) {
        new Sally<Sally, String>();
    }
}
Post a Comment