如何检查一个 ParameterizedType 是否代表另一个 ParameterizedType 的子类型?

How to check if one ParameterizedType represents a sub-type of another ParameterizedType?

给出下面的代码片段,对于 POJO class 的每个字段,有没有办法检查类型是否是整数列表?这里真正的问题是类型参数,因为很容易通过 instanceofisAssignableFrom.

检查它是否是一个列表

main 中的行是我目前所发现的,但它不适用于那些具有更复杂 class 层次结构的类型。

public class Test {

    public static void main(String[] args) throws NoSuchFieldException {
        // to check if field f1 is an integer list
        Field field = Pojo.class.getField("f1");
        if (List.class.isAssignableFrom(field.getType())) {
            Type type = field.getGenericType();
            if (type instanceof ParameterizedType) {
                ParameterizedType pt = (ParameterizedType) type;
                if (pt.getActualTypeArguments()[0].equals(Integer.class)) {
                    System.out.println("Yes, it's an integer list");
                }
            }
        }

    }

    public static class Pojo {
        public List<Integer> f1;
        public ArrayList<Integer> f2;
        public C1 f3;
        public C2<Integer> f4;
        public C3<Integer> f5; // notice, this is not an integer list, it's a string list
        public C4<Integer> f6;
        public C5<String, Integer> f7;
    }

    public static class C1 implements List<Integer> {...}

    public static class C2<T> implements List<T> {...}

    public static class C3<T> implements List<String> {...}

    public static class C4<T> implements List<Integer> {...}

    public static class C5<T1, T2> implements List<T2> {...}
}

你是对的,因为擦除,这不是一个容易的问题。不过我觉得也不是不能解决

基本原则应该是遵循并用实际参数类型替换类型变量一直到List<E>

考虑这两个 classes:

class B<T> extends AbstractList<T>{}
class A<E> extends B<E>{}

B的声明中使用的类型变量名是T,而从A中扩展时,使用E。因此,我们不能依赖声明的名称。

  • 我们必须在子class中遵循类型变量名称,用父class或接口中的实际值替换变量。
  • 这必须从 Field 开始,直到我们达到 List<E>。 (我不确定当 List 通过不止一条路径进入层次结构时会发生什么。因此,下面没有处理这种情况。)
  • 最后把List中的E替换掉,就可以判断是不是List<Integer>了。

这是执行此操作的代码。这是相当多的行,但适用于您添加的案例(以及我这边的更多案例)。实施过程中可能存在一些缺陷,但这种方法应该有效。

public class ParameterizedTypeHierarchy{
    /* When a conclusion is reached - negative or positive - about a field, one of these exceptions is thrown to
     * quickly return to the main(). */
    private static class NotListOfIntegerException extends RuntimeException{
        private Type elementType;

        public NotListOfIntegerException( Type elementType ){
            this.elementType = elementType;
        }
    }
    private static class IsAListOfIntegerException extends RuntimeException{}

    public static void main( String[] args ) throws NoSuchFieldException{
        List<String> fields = Arrays.asList( "f0", "f1", "f2", "c1", "c2", "c3", "c4", 
                                            "c5", "c5Neg1", "c6", "c6Neg1", "c7", "c7Neg1", "c7Neg2" );

        for( String f : fields ){
            // to check if field f1 is an integer list
            Field field = Pojo.class.getField( f );

            try{
                check( field );
            }
            catch( IsAListOfIntegerException e ){
                System.out.println( f + " (" + field.getType().getSimpleName() + ") is a List<Integer>" );
            }
            catch( NotListOfIntegerException e ){
                if( e.elementType == null ) System.out.println( f + " (" + field.getType().getSimpleName() + ") is NOT a List." );
                else System.out.println( f + " (" + field.getType().getSimpleName() + ") is NOT a List<Integer>. It is List<" + e.elementType.getTypeName() + ">." );
            }
            catch( Exception e ){
                e.printStackTrace();
            }
        }
    }

    private static boolean check( Field f ){
        Type type = f.getGenericType();
        if( type instanceof ParameterizedType ){
            /* Parameterized type field. */
            if( isList( (ParameterizedType) type, null, null ) ) return true;
        }
        else if( type instanceof Class ){
            /* Field's type takes no parameters. */
            return fromClass( (Class<?>) type );
        }

        return false;
    }

    private static boolean fromClass( Class<?> type ){
        /* For class there are ways in which a List could be one of its parents: interface implementations
         * or the super class indirectly being a child of List. */
        Type[] intfs = type.getGenericInterfaces();
        if( intfs != null && intfs.length > 0 ){
            for( Type intf : intfs ){
                if( intf instanceof ParameterizedType && isList( (ParameterizedType) intf, type, null ) ) return true;
            }
        }

        Type st = type.getGenericSuperclass();
        
        /* If there is no super class or the super is Object, we can conclude in the negative. */
        if( st == null || Object.class == st ) throw new NotListOfIntegerException( null );

        if( st instanceof ParameterizedType ){
            if( isList( (ParameterizedType) st, type, null ) ) return true;
        }

        return false;
    }

    private static boolean isList( ParameterizedType pt, Class<?> extendingEntity, Type[] types ){
        /* This parameterized type needs to be a list. Else return. */
        Type raw = pt.getRawType();
        if( raw instanceof Class && !List.class.isAssignableFrom( (Class<?>) raw ) ) return false;

        Type[] listParamTypes = pt.getActualTypeArguments();
        
        /* If this is directly List.class, then we can decide here itself. */
        if( raw == List.class ) return listParamTypes[ 0 ] == Integer.class;

        /* This is a parameterized class that implements List. However, the extending class's parameter(s) need not be for 
         * List. Hence, we have get the RIGHT parameters for the parameterized type and check against them. */
        Type[] replaced = replaceTypeVars( pt, extendingEntity, types );
        Class<?> c = (Class<?>) raw;
        return classExtList( c, replaced );
    }

    private static Type[] replaceTypeVars( ParameterizedType pt, Class<?> impl, Type[] types ){
        Map<String, Type> repl = replacements( impl, types );

        Type raw = pt.getRawType();

        Type[] rawTypeParams = null;
        if( raw instanceof Class<?> ){
            Class<?> c = (Class<?>) raw;
            Type[] actual = pt.getActualTypeArguments();
            if( actual == null || actual.length == 0 ) return null;

            rawTypeParams = new Type[ actual.length ];
            for( int i = 0; i < actual.length; i++ ){
                Type tv = actual[ i ];

                Type val = null;
                if( !( tv instanceof TypeVariable ) ){
                    rawTypeParams[ i ] = actual[ i ];
                }
                else{
                    if( ( val = repl.get( ( (TypeVariable<?>) tv ).getName() ) ) == null )
                        rawTypeParams[ i ] = tv;
                    else
                        rawTypeParams[ i ] = val;
                }
            }
        }

        return rawTypeParams;
    }

    /* Replaces the type variables declared on a class with the actual parameters passed. */
    private static Map<String, Type> replacements( Class<?> c, Type[] types ){
        if( c == null ) return Collections.emptyMap();

        TypeVariable<?>[] tps = c.getTypeParameters();
        if( tps == null || tps.length == 0 ) return Collections.emptyMap();

        Map<String, Type> map = new HashMap<>();
        for( int i = 0; i < tps.length; i++ ){
            /* Skip TypeVariable instances. We want only replaced ones. */
            if( types[ i ] instanceof TypeVariable ) continue;

            TypeVariable<?> tv = tps[ i ];
            map.put( tv.getName(), types[ i ] );
        }

        return map;
    }

    private static boolean classExtList( Class<?> c, Type[] types ){
        TypeVariable<?>[] params = c.getTypeParameters();

        Type[] intfs = c.getGenericInterfaces();
        for( Type intf : intfs ){
            /* If this interface is List and is taking Integer as param, great. */
            if( intf instanceof ParameterizedType ){
                ParameterizedType pt = (ParameterizedType) intf;
                if( pt.getRawType() == List.class ){
                    /* If the type argument is Integer.class, we are done. */
                    if( pt.getActualTypeArguments()[ 0 ] == Integer.class ) throw new IsAListOfIntegerException();

                    /* Type argument is the type variable itself. We have to check which type variable 
                     * was passed to List. */
                    for( int i = 0; i < params.length; i++ ){
                        if( params[ i ].getName().equals( pt.getActualTypeArguments()[ 0 ].getTypeName() ) ){
                            if( types[ i ] == Integer.class ) throw new IsAListOfIntegerException();
                            throw new NotListOfIntegerException( types[ i ] );
                        }
                    }
                }
            }
        }

        Type st = c.getGenericSuperclass();
        if( st instanceof ParameterizedType ) return isList( (ParameterizedType) st, c, types );

        return false;
    }

    public static class Pojo {
        public Object f0;
        public List<Integer> f1;
        public ArrayList<Integer> f2;
        public C1 c1;
        public C2<Integer> c2;
        public C3<Integer> c3; // notice, this is not an integer list, it's a string list
        public C4<Integer> c4;
        public C5<String, Integer> c5;
        public C5<String, String> c5Neg1;
        public C6<String, Integer> c6;
        public C6<String, String> c6Neg1;
        public C7<String, Integer> c7;
        public C7<String, String> c7Neg1;
        public C7<String, ?> c7Neg2;
    }
    
    private static interface A<E>{}

    private static class B<T> extends AbstractList<T> {
        @Override
        public T get( int index ){ return null; }

        @Override
        public int size(){ return 0; }
    }
    
    public static class C1 extends B<Integer> {}

    public static class C2<T> extends B<T> {}

    public static class C3<T> extends B<String> {}

    public static class C4<T> extends B<Integer> {}

    public static class C5<T1, T2> extends B<T2> {}
    
    public static class C6<T1, T2> implements List<T2> {...}

    public static class C7<T1, T2> implements List<T2>, A<T1>{...}
}

谢谢 Kumar,扭曲类型变量似乎是个好方法,我对 Kumar 的代码做了一点重构,这样我就可以获得实际的类型参数,把它留在这里以备 soemone 需要。

// example, getTypeArg(field.getGenericType(), null, List.class, 0),
// or, getTypeArg(field.getGenericType(), null, Map.class, 1),
public static Type getTypeArg(Type type, Map<TypeVariable<?>, Type> typeVar2RealType,
                              Class<?> targetClazz, int targetTypeArgIndex) {
    if (targetClazz == null) {
        throw new RuntimeException("Missing target class");
    }
    if (targetTypeArgIndex < 0) {
        throw new RuntimeException("Invalid target type argument index");
    }
    if (type == null || type.equals(Object.class)) {
        return null;
    }
    Class<?> raw;
    Map<TypeVariable<?>, Type> map = null;
    if (type instanceof ParameterizedType) {
        ParameterizedType pt = (ParameterizedType) type;
        raw = (Class<?>) pt.getRawType();
        if (!targetClazz.isAssignableFrom(raw)) {
            return null;
        }
        map = new HashMap<>();
        for (int i = 0; i < pt.getActualTypeArguments().length; i++) {
            TypeVariable<?> var = raw.getTypeParameters()[i];
            Type typeArg = pt.getActualTypeArguments()[i];
            Type real = typeArg;
            if (typeArg instanceof TypeVariable) {
                TypeVariable<?> tv = (TypeVariable<?>) typeArg;
                if (typeVar2RealType != null && typeVar2RealType.containsKey(tv)) {
                    real = typeVar2RealType.get(tv);
                }
            }
            if (raw.equals(targetClazz) && i == targetTypeArgIndex) {
                return real;
            }
            map.put(var, real);
        }
    } else if (type instanceof Class) {
        raw = (Class<?>) type;
        if (!targetClazz.isAssignableFrom(raw)) {
            return null;
        }
    } else {
        return null; // ?
    }
    //
    for (Type parent : getGenericParents(raw)) {
        Type ret = getTypeArg(parent, map, targetClazz, targetTypeArgIndex);
        if (ret != null) {
            return ret;
        }
    }
    return null;
}

public static List<Type> getGenericParents(Class<?> clazz) {
    List<Type> parents = new ArrayList<>();
    if (clazz.getGenericInterfaces().length > 0) {
        parents = new ArrayList<>(Arrays.asList(clazz.getGenericInterfaces()));
    }
    if (clazz.getGenericSuperclass() != null) {
        parents.add(clazz.getGenericSuperclass());
    }
    return parents;
}