javagenericsreflectionjava-8type-annotation

How to get generic type information from getAnnotatedParameterTypes() in Java 8?


It seems like getAnnotatedParameterTypes() returns an array of AnnotatedTypes holding raw, rather than generic, types. For example:

public <T> void genericMethod(T t) {
}

@Test
public void testAnnotatedTypes() throws ReflectiveOperationException {
    Method method = getClass().getMethod("genericMethod", Object.class);

    Type type = method.getGenericParameterTypes()[0];
    assertTrue(type instanceof TypeVariable);

    AnnotatedType annotatedType = method.getAnnotatedParameterTypes()[0];

    // This fails; annotatedType implements only AnnotatedType
    assertTrue(annotatedType instanceof AnnotatedTypeVariable);

    // This fails too; type is a TypeVariable while annotatedType.getType() is
    // Object.class
    assertEquals(type, annotatedType.getType());
}

What's the reason for the disagreement with getGenericParameterTypes()?


Solution

  • There's a bug report about this, and it has since been fixed.

    There's a difference between Method#getGenericParameterTypes() and Method#getAnnotatedParameterTypes().

    The former makes guarantees about the types it returns

    If a formal parameter type is a parameterized type, the Type object returned for it must accurately reflect the actual type parameters used in the source code.

    If a formal parameter type is a type variable or a parameterized type, it is created. Otherwise, it is resolved.

    while the latter doesn't, not clearly at least:

    Returns an array of AnnotatedType objects that represent the use of types to specify formal parameter types of the method/constructor represented by this Executable.

    We have to assume that getAnnotatedParameterTypes() returns the erased types (although it may not have been intended that way). The unbounded type variable T is erased to Object. If you had <T extends Foo>, it would be erased to Foo.

    As for the comments, about getting the annotation from a type argument in a method parameter, there's no way given the above. One would think it works as it does for fields.

    public static void main(String[] args) throws Exception {
        Field field = Example.class.getField("field");
        AnnotatedParameterizedType annotatedParameterizedType = (AnnotatedParameterizedType) field
                .getAnnotatedType();
    
        System.out.println(annotatedParameterizedType
                .getAnnotatedActualTypeArguments()[0].getType());
        System.out.println(Arrays.toString(annotatedParameterizedType
                .getAnnotatedActualTypeArguments()[0].getAnnotations()));
    }
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(value = { ElementType.TYPE_USE })
    @interface Bar {
    }
    
    public List<@Bar String> field;
    

    which prints

    class java.lang.String
    [@com.example.Example$Bar()]
    

    I very much think it's a bug that needs fixing and will be following the bug report linked above.