pixel
pixel

Reputation: 26503

How to convert Type with generics into a Class in java?

I have a method objet.

I would like to extract return Type with generics and convert it to a Class in order to pass such information into Spring PropertyResolver.

Type type = myMethod.getGenericReturnType();
Class<?> returnType = /* ??? */;
environment.getProperty(key, returnType);

Upvotes: 3

Views: 6898

Answers (2)

Valentin Ruano
Valentin Ruano

Reputation: 2809

In practice return Type instances must be one of the following: Class (E.g. String), GenericArrayType (E.g String[] or T[] or List<T>[]), TypeVariable (E.g. T) or ParametrizedType (E.g. List<String> or List<T>). In addition Type can also be WildcardType (E.g. ? in List<?>) but these cannot be use directly as return types.

The following code tries to resolve the class given a instance based of its sub interface amongst those 5. Rarely if ever a Type won't extend any of 5 in which case we simply say that we cannot proceed with an UnsupportedOperationException. For example you can create your own synthetic Type extending class but why would you want to ever do that?

public static Class<?> type2Class(Type type) {
    if (type instanceof Class) {
       return (Class<?>) type;
    } else if (type instanceof GenericArrayType) {
       // having to create an array instance to get the class is kinda nasty 
       // but apparently this is a current limitation of java-reflection concerning array classes.
       return Array.newInstance(type2Class(((GenericArrayType)type).getGenericComponentType()), 0).getClass(); // E.g. T[] -> T -> Object.class if <T> or Number.class if <T extends Number & Comparable>
    } else if (type instanceof ParameterizedType) {
       return type2Class(((ParameterizedType) type).getRawType()); // Eg. List<T> would return List.class
    } else if (type instanceof TypeVariable) {
       Type[] bounds = ((TypeVariable<?>) type).getBounds();
       return bounds.length == 0 ? Object.class : type2Class(bounds[0]); // erasure is to the left-most bound.
    } else if (type instanceof WildcardType) {
       Type[] bounds = ((WildcardType) type).getUpperBounds();
       return bounds.length == 0 ? Object.class : type2Class(bounds[0]); // erasure is to the left-most upper bound.
    } else { 
       throw new UnsupportedOperationException("cannot handle type class: " + type.getClass());
    }
} 

Notice that the code is untested so it might contain compilation errors. Also I'm not sure how the GenericArrayType would behave with multi-dimensional array types like T[][] (perhaps it would return Object[] rather than Object[][] if <T> so we need to do additional work here). Please let me know if any corrections are needed.

In the end what we are trying to do here is to calculate the Erasure class given a Type I wonder whether there is some "standard" code to just do that, perhaps part of Sun/Oracle compiler or code analyzer tools and you just can use their utilities and save yourself the hassle of coding and maintained it ... I didn't find anything thru a quick look.

Upvotes: 5

davidxxx
davidxxx

Reputation: 131486

You can use a workaround to convert java.lang.reflect.Type returned by Method.getGenericReturnType() to the Class of the generic.

String parsing :

final String typeName = method.getGenericReturnType().getTypeName();
Pattern pattern = Pattern.compile("<(.*)>");
final Matcher matcher = pattern.matcher(typeName);
if (matcher.find()) {
    String className = matcher.group(1);
    Class<?> clazz = Class.forName(className);        
}

You could also downcast java.lang.reflect.Type to the concrete class used at runtime : sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl but I suppose that it may change according to the JVM.

final Type genericReturnType = method.getGenericReturnType();
sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl typeImpl = (ParameterizedTypeImpl) genericReturnType;
String className = typeImpl.getActualTypeArguments()[0].getTypeName();
Class<?> clazz = Class.forName(className);

Upvotes: 1

Related Questions