Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inferring java generics parameters at runtime using reflection

I have a method with the following signature:

// Converts a json string to a list of objects
// Assumption: json is an array, and all items in the list are of the same type
public <T> List<T> getListFromJson( String json, Class<T> itemType, List<T> defValue ) {
    final ArrayList<T> list = new ArrayList<T>();

    for( JsonElement e : parser.parse(json).getAsJsonArray())
        list.add( (T) (
                Number.class.isAssignableFrom(itemType) ? e.getAsNumber() :
                Boolean.class.isAssignableFrom(itemType) ? e.getAsBoolean() :
                Character.class.isAssignableFrom(itemType) ? e.getAsCharacter() :
                String.class.isAssignableFrom(itemType) ? e.getAsString() :
                JsonElement.class.isAssignableFrom(itemType) ? e :
                null
            )
        );

    return list;
}

It reads the json string and converts it to a list of the appropriate type of object, eg. Integer, String, etc.

Is there a robust way to remove the Class<T> argument from the method by inferring it from the List<T> parameter? Eg. Is there a way I can change the method signature to the following without losing functionality?

public <T> List<T> getListFromJson( String json, List<T> defValue ) {
    ...
}

It seems like the solution is going to require some fancy manipulation of ParameterizedType. I've looked at the following, but either I'm using these methods incorrectly or they're not doing what I expect:

  • getTypeArguments() from http://www.artima.com/weblogs/viewpost.jsp?thread=208860
  • getActualTypeArguments() from https://code.google.com/p/aphillips/source/browse/commons-lang/trunk/src/main/java/com/qrmedia/commons/lang/ClassUtils.java
like image 569
emmby Avatar asked Feb 25 '23 15:02

emmby


2 Answers

Due to type erasure, you definitely can't "infer" what T is--it doesn't even exist at runtime. The closest you could come is inspect the values in defValue (if it has values) and get the class of the elements there.

Class<?> tType = defValue.get(0).getClass();

if (Boolean.class.isAssignableFrom(tType)) { //...  

Edit

With regards to your thinking of using reflection like getTypeArguments(), etc. Those only provide data for declared types, never actual types. So for example, if you got a handle to the Method object and called getTypeParameters(), you'd just get an array containing a type object representing T--not the actual type that T represents at some specific runtime invocation.

like image 188
Mark Peters Avatar answered Feb 28 '23 03:02

Mark Peters


The only way I know of to guarantee to have access to the type at run time is to make it a parameter to the object constructor thus:

class MyClass<T> {
    private final Class<T> clazz;
    public MyClass(Class<T> clazz) {
        this.clazz=clazz;
    }
}

You then have to pass the class when you instantiate the object:

MyClass<String> object = new MyClass<String>(String.class);

Obviously, in your case, you have what is effectively a static utility method and no object, so I think you're stuck with either the Class parameter or else some kind of template object.

like image 40
Bill Michell Avatar answered Feb 28 '23 05:02

Bill Michell