2

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);
Michael
  • 34,340
  • 9
  • 58
  • 100
pixel
  • 21,352
  • 30
  • 113
  • 196
  • Have you looked at this yet? https://stackoverflow.com/questions/3403909/get-generic-type-of-class-at-runtime – Cardinal System Jan 02 '18 at 09:47
  • Since `getProperty` only accepts a `Class` parameter, why not just use `myMethod.getReturnType()`? – Andy Turner Jan 02 '18 at 09:47
  • @AndyTurner I will loose generics information by doing that – pixel Jan 02 '18 at 09:49
  • @pixel but since `getProperty` only accepts a `Class` parameter - and there's no such thing as `List.class`, for example, only `List.class` - how would you do anything else? – Andy Turner Jan 02 '18 at 09:50
  • @AndyTurner you're right - I need to do something more complicated in order to convert value with generics. I've found this code that is doing something similar to what I want to achieve: https://github.com/lviggiano/owner/blob/master/owner/src/main/java/org/aeonbits/owner/Converters.java – pixel Jan 03 '18 at 15:50

2 Answers2

4

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.

Caranown
  • 83
  • 9
Valentin Ruano
  • 2,574
  • 15
  • 28
1

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);
davidxxx
  • 104,693
  • 13
  • 159
  • 179