简体   繁体   English

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

[英]Java, how to check if one ParameterizedType represents a sub-type of another ParameterizedType?

Given below code snippet, for each field of Pojo class, is there a way to check if the type is an integer list or not?给出下面的代码片段,对于 Pojo class 的每个字段,有没有办法检查类型是否是 integer 列表? The real problem here is the type argument, since it's quite easy to check if it's a list via instanceof or isAssignableFrom .这里真正的问题是类型参数,因为通过instanceofisAssignableFrom很容易检查它是否是一个列表。

Lines in main is what I have found so far, but it does not work for those types with more complex class hierarchy. main中的行是我迄今为止发现的,但它不适用于具有更复杂 class 层次结构的类型。

Please shed some light here, many thanks.请在这里提供一些启发,非常感谢。

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> {...}
}

You are right, this is not an easy problem because of erasure.你是对的,因为擦除,这不是一个容易的问题。 However, I think it is not unsolvable.但是,我认为这不是无法解决的。

The fundamental principle should be to follow the type variables all the way to List<E> .基本原则应该是一直遵循类型变量到List<E>

Consider these two classes:考虑这两个类:

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

The type variable name used in the declaration of B is T , while when it is extended from in A , E is used.B的声明中使用的类型变量名称是T ,而当它从A中扩展时,使用E Hence, we cannot rely upon the declared name.因此,我们不能依赖声明的名称。

  • We have to follow the type variable name in the child class, replacing the variables with actual values in the parent class or interface.我们必须遵循子 class 中的类型变量名称,将变量替换为父 class 或接口中的实际值。
  • This has to start from the Field till we hit List<E> .这必须从Field开始,直到我们点击List<E> (I am not sure what happens when List comes in the heirarchy through more than one path. Hence, that case hasn't been handled below.) (我不确定当 List 通过不止一条路径进入层次结构时会发生什么。因此,下面没有处理这种情况。)
  • When we replace E in List finally, we can conclude if it is List<Integer> or not.当我们最后替换List中的E时,我们可以断定它是否是List<Integer>

Here is a code that does this.这是执行此操作的代码。 It is quite some number of lines but is working for the cases you added (and a few more from my side).这是相当多的行,但适用于您添加的案例(以及我身边的更多案例)。 There may be flaws in the implementation here and there, but this approach should work.这里和那里的实现可能存在缺陷,但这种方法应该有效。

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>{...}
}

Thanks Kumar, twisting type variables seems a good way to go, I made a bit refactor on Kumar's code, so that I can get the actual type argument, leave it here in case soemone needs it.感谢 Kumar,扭曲类型变量似乎是 go 的好方法,我对 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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何使用Java反射检查ParameterizedType参数是否扩展了特定的类 - How to use Java reflection to check if ParameterizedType argument extends a specific class 如何使用Java Reflection从ParameterizedType获取GenericDeclaration的类型? - How to get the Type of GenericDeclaration from ParameterizedType using Java Reflection? java.util.Set无法通过ParameterizedType检查 - java.util.Set is is failing ParameterizedType check 如何在Java中获取ParameterizedType的通用容器类 - How to get generic container class of a ParameterizedType in java 从泛型获取ParameterizedType的类型 - Get Type of ParameterizedType from generic 从现有的ParameterizedType创建另一个类的Parameterized Type - Create Parameterized Type of another class from existing ParameterizedType 如何确定注入的CDI bean的parameterizedType的具体类型 - How to determine the concrete type of the parameterizedType of injected CDI bean 给定ParameterizedType和Type,如何重新参数化第一个类型? - Given a ParameterizedType and a Type, how can I reparameterize the first type? 给定ParameterizedType,如何创建此类型的实例? - Given a ParameterizedType, how do I create an instance of this type? Javax注解处理:检查注解类型是否是另一种类型的子类型 - Javax Annotation Processing: Check if an annotated type is a sub-type of another type
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM