如何确定 Java 中泛型字段的类型?

Posted

技术标签:

【中文标题】如何确定 Java 中泛型字段的类型?【英文标题】:How can I determine the type of a generic field in Java? 【发布时间】:2009-12-08 17:00:07 【问题描述】:

我一直在尝试确定类中字段的类型。我已经看过所有的内省方法,但还没有完全弄清楚如何去做。这将用于从 java 类生成 xml/json。我在这里查看了一些问题,但没有找到我真正需要的。

例子:

class Person 
    public final String name;
    public final List<Person> children;

当我编组这个对象时,我需要知道chidren 字段是Person 类型的对象列表,所以我可以正确编组它。

我试过了

for (Field field : Person.class.getDeclaredFields()) 
    System.out.format("Type: %s%n", field.getType());

但这只会告诉我这是List,而不是Persons 中的List

谢谢

【问题讨论】:

列表 ?除非我没有抓住重点。 【参考方案1】:

查看 Java 教程 Trail: The Reflection API 中的 Obtaining Field Types。

基本上,您需要做的是获取您班级的所有java.lang.reflect.Field并在每个人上调用Field#getType()(检查下面的编辑)。要获取所有对象字段,包括公共、受保护、包和私有访问字段,只需使用Class.getDeclaredFields()。像这样的:

for (Field field : Person.class.getDeclaredFields()) 
    System.out.format("Type: %s%n", field.getType());
    System.out.format("GenericType: %s%n", field.getGenericType());

编辑:正如wowest在评论中指出的那样,您实际上需要调用Field#getGenericType(),检查返回的Type是否是ParameterizedType,然后相应地获取参数.使用ParameterizedType#getRawType()ParameterizedType#getActualTypeArgument() 分别获取ParameterizedType 的类型参数的原始类型和数组。下面的代码演示了这一点:

for (Field field : Person.class.getDeclaredFields()) 
    System.out.print("Field: " + field.getName() + " - ");
    Type type = field.getGenericType();
    if (type instanceof ParameterizedType) 
        ParameterizedType pType = (ParameterizedType)type;
        System.out.print("Raw type: " + pType.getRawType() + " - ");
        System.out.println("Type args: " + pType.getActualTypeArguments()[0]);
     else 
        System.out.println("Type: " + field.getType());
    

并且会输出:

Field: name - Type: class java.lang.String
Field: children - Raw type: interface java.util.List - Type args: class foo.Person

【讨论】:

帕斯卡——你非常接近。你想要 Type type = Field.getGenericType();然后检查它是否是ParameterizedType,然后获取参数。这最终变成了一个兔子洞——提问者需要定义限制或准备编写一些漂亮的代码。 感谢 wowest,这正是我需要与 ParameterizedType.getRawType 和 ParameterizedType.getActualArguments 结合使用的。 我不知道如何将您的评论标记为正确答案,所以我只回答我自己的问题,以便发布示例 @Juan Mendes,您应该会在每个答案的左侧看到一个勾号图标,在投票编号旁边,只需单击属于正确答案的那个,它就会变成绿色。 我知道我可以将此答案标记为正确答案。然而,直到 Pascal 编辑它以同意 wowest 的评论之前,它是不正确的。无论如何,我在下面列出的示例包含了我需要的所有信息,但在这么多人帮助我之后,我不想将自己的答案标记为正确答案。 Pascal 的示例仍然缺少关键方法(ParameterizedType.getRawType 和 ParameterizedType.getActualArguments?)。 Pascal,您能否编辑示例以显示正在使用的这两种方法?【参考方案2】:

这是一个回答我问题的例子

class Person 
  public final String name;
  public final List<Person> children;  


//in main
Field[] fields = Person.class.getDeclaredFields();
for (Field field : fields) 
  Type type = field.getGenericType();
  System.out.println("field name: " + field.getName());
  if (type instanceof ParameterizedType) 
    ParameterizedType ptype = (ParameterizedType) type;
    ptype.getRawType();
    System.out.println("-raw type:" + ptype.getRawType());
    System.out.println("-type arg: " + ptype.getActualTypeArguments()[0]);
   else 
    System.out.println("-field type: " + field.getType());
  

这个输出

字段名称:名称 -字段类型:类 java.lang.String 字段名称:儿童 -原始类型:接口 java.util.List -type arg:com.blah.Person 类

【讨论】:

【参考方案3】:

我还没有找到任何通过继承层确定通用字段类型的框架,所以我写了一些方法:

这个逻辑通过字段信息和当前对象类来判断类型。

清单 1 - 逻辑:

public static Class<?> determineType(Field field, Object object) 
    Class<?> type = object.getClass();
    return (Class<?>) getType(type, field).type;


protected static class TypeInfo 
    Type type;
    Type name;

    public TypeInfo(Type type, Type name) 
        this.type = type;
        this.name = name;
    



private static TypeInfo getType(Class<?> clazz, Field field) 
    TypeInfo type = new TypeInfo(null, null);
    if (field.getGenericType() instanceof TypeVariable<?>) 
        TypeVariable<?> genericTyp = (TypeVariable<?>) field.getGenericType();
        Class<?> superClazz = clazz.getSuperclass();

        if (clazz.getGenericSuperclass() instanceof ParameterizedType) 
            ParameterizedType paramType = (ParameterizedType) clazz.getGenericSuperclass();
            TypeVariable<?>[] superTypeParameters = superClazz.getTypeParameters();
            if (!Object.class.equals(paramType)) 
                if (field.getDeclaringClass().equals(superClazz)) 
                    // this is the root class an starting point for this search
                    type.name = genericTyp;
                    type.type = null;
                 else 
                    type = getType(superClazz, field);
                
            
            if (type.type == null || type.type instanceof TypeVariable<?>) 
                // lookup if type is not found or type needs a lookup in current concrete class
                for (int j = 0; j < superClazz.getTypeParameters().length; ++j) 
                    TypeVariable<?> superTypeParam = superTypeParameters[j];
                    if (type.name.equals(superTypeParam)) 
                        type.type = paramType.getActualTypeArguments()[j];
                        Type[] typeParameters = clazz.getTypeParameters();
                        if (typeParameters.length > 0) 
                            for (Type typeParam : typeParameters) 
                                TypeVariable<?> objectOfComparison = superTypeParam;
                                if(type.type instanceof TypeVariable<?>) 
                                    objectOfComparison = (TypeVariable<?>)type.type;
                                
                                if (objectOfComparison.getName().equals(((TypeVariable<?>) typeParam).getName())) 
                                    type.name = typeParam;
                                    break;
                                
                            
                        
                        break;
                    
                
            
        
     else 
        type.type = field.getGenericType();
    

    return type;

清单 2 - 样本/测试:

class GenericSuperClass<E, T, A> 
    T t;
    E e;
    A a;
    BigDecimal b;


class GenericDefinition extends GenericSuperClass<Integer, Integer, Integer> 



@Test
public void testSimpleInheritanceTypeDetermination() 
    GenericDefinition gd = new GenericDefinition();
    Field field = ReflectionUtils.getField(gd, "t");
    Class<?> clazz = ReflectionUtils.determineType(field, gd);
    Assert.assertEquals(clazz, Integer.class);
    field = ReflectionUtils.getField(gd, "b");
    clazz = ReflectionUtils.determineType(field, gd);
    Assert.assertEquals(clazz, BigDecimal.class);


class MiddleClass<A, E> extends GenericSuperClass<E, Integer, A>  

// T = Integer, E = String, A = Double
class SimpleTopClass extends MiddleClass<Double, String>  

@Test
public void testSimple2StageInheritanceTypeDetermination() 
    SimpleTopClass stc = new SimpleTopClass();
    Field field = ReflectionUtils.getField(stc, "t");
    Class<?> clazz = ReflectionUtils.determineType(field, stc);
    Assert.assertEquals(clazz, Integer.class);
    field = ReflectionUtils.getField(stc, "e");
    clazz = ReflectionUtils.determineType(field, stc);
    Assert.assertEquals(clazz, String.class);
    field = ReflectionUtils.getField(stc, "a");
    clazz = ReflectionUtils.determineType(field, stc);
    Assert.assertEquals(clazz, Double.class);


class TopMiddleClass<A> extends MiddleClass<A, Double>  

// T = Integer, E = Double, A = Float
class ComplexTopClass extends TopMiddleClass<Float> 

@Test void testComplexInheritanceTypDetermination() 
    ComplexTopClass ctc = new ComplexTopClass();
    Field field = ReflectionUtils.getField(ctc, "t");
    Class<?> clazz = ReflectionUtils.determineType(field, ctc);
    Assert.assertEquals(clazz, Integer.class);
    field = ReflectionUtils.getField(ctc, "e");
    clazz = ReflectionUtils.determineType(field, ctc);
    Assert.assertEquals(clazz, Double.class);
    field = ReflectionUtils.getField(ctc, "a");
    clazz = ReflectionUtils.determineType(field, ctc);
    Assert.assertEquals(clazz, Float.class);


class ConfusingClass<A, E> extends MiddleClass<E, A> 
// T = Integer, E = Double, A = Float ; this class should map between a and e
class TopConfusingClass extends ConfusingClass<Double, Float> 

@Test
public void testConfusingNamingConvetionWithInheritance() 
    TopConfusingClass tcc = new TopConfusingClass();
    Field field = ReflectionUtils.getField(tcc, "t");
    Class<?> clazz = ReflectionUtils.determineType(field, tcc);
    Assert.assertEquals(clazz, Integer.class);
    field = ReflectionUtils.getField(tcc, "e");
    clazz = ReflectionUtils.determineType(field, tcc);
    Assert.assertEquals(clazz, Double.class);
    field = ReflectionUtils.getField(tcc, "a");
    clazz = ReflectionUtils.determineType(field, tcc);
    Assert.assertEquals(clazz, Float.class);
    field = ReflectionUtils.getField(tcc, "b");
    clazz = ReflectionUtils.determineType(field, tcc);
    Assert.assertEquals(clazz, BigDecimal.class);


class Pojo 
    Byte z;


@Test
public void testPojoDetermineType() 
    Pojo pojo = new Pojo();
    Field field = ReflectionUtils.getField(pojo, "z");
    Class<?> clazz = ReflectionUtils.determineType(field, pojo);
    Assert.assertEquals(clazz, Byte.class);

我期待听到您的反馈!

【讨论】:

谢谢!这种方法帮助很大。我建议添加检查 type.type 是否是ParameterizedTypeImpl 的实例,在这种情况下.getRawType() 方法会产生该类型。当类型为List&lt;Double&gt; 时,这将获取List。 (否则可能会得到ParameterizedTypeImpl cannot be cast to java.lang.Class【参考方案4】:

拿着这个sn-p:

 for (Field field : Person.class.getFields()) 
        System.out.println(field.getType());
 

关键类是Field

【讨论】:

只有Class.getFields 的公共字段(包括静态)。也将是擦除类型。 那我误解了问题【参考方案5】:

这是我的看法。它不能处理所有可能的情况(并且肯定有一些错误),但它确实处理了到目前为止我的代码中出现的所有情况。这包括这些声明,这对于许多用例来说应该是一个好的开始:

  private int                                                primitiveField1;

  private Object                                             field1;
  private List<Integer>                                      field2;
  private Map<Integer, String>                               field3;
  private Map<? extends String, List<Map<Class<?>, Object>>> field4;

  private char[]                                             array1;
  private Character[]                                        array2;
  private Class<? extends Integer>[]                         array3;
  private List<Integer>[]                                    array4;

  private InnerClass<String>                                 innerClass;

实施:

  public static String getDeclaration(Field field) 
    return getDeclaration(field.getGenericType());
  

  private static String getDeclaration(Type genericType) 
    if(genericType instanceof ParameterizedType) 
      // types with parameters
      ParameterizedType parameterizedType = (ParameterizedType) genericType;
      String declaration = parameterizedType.getRawType().getTypeName();
      declaration += "<";

      Type[] typeArgs = parameterizedType.getActualTypeArguments();

      for(int i = 0; i < typeArgs.length; i++) 
        Type typeArg = typeArgs[i];

        if(i > 0) 
          declaration += ", ";
        

        // note: recursive call
        declaration += getDeclaration(typeArg);
      

      declaration += ">";
      declaration = declaration.replace('$', '.');
      return declaration;
    
    else if(genericType instanceof Class<?>) 
      Class<?> clazz = (Class<?>) genericType;

      if(clazz.isArray()) 
        // arrays
        return clazz.getComponentType().getCanonicalName() + "[]";
      
      else 
        // primitive and types without parameters (normal/standard types)
        return clazz.getCanonicalName();
      
    
    else 
      // e.g. WildcardTypeImpl (Class<? extends Integer>)
      return genericType.getTypeName();
    
  

【讨论】:

你能评论一下你能做什么而 javaBeCool 不能吗?我想知道两者的混合是否是处理递归泛型和继承所需要的。见***.com/a/19363555/227299 @JuanMendes javaBeCool 似乎解决了一个不同的用例。我的答案是将字段声明作为字符串获取,我在代码生成项目中使用它。我原来的问题被标记为这个问题的重复 - 这就是我在此处发布的原因 (***.com/q/45083186/1124509)。【参考方案6】:

正如 dfa 指出的那样,您可以使用 java.lang.reflect.Field.getType 获取已擦除类型。您可以使用Field.getGenericType 获得泛型类型(可能有通配符和绑定的泛型参数以及各种疯狂)。您可以通过Class.getDeclaredFields 获取字段(Class.getFields 将为您提供公共字段(包括 supertpye 的字段)- 毫无意义)。要获取基本类型字段,请通过Class.getSuperclass。请注意检查来自 Field.getModifiers 的修饰符 - 静态字段可能不会对您感兴趣。

【讨论】:

【参考方案7】:

field.getGenericType() 方法返回对Type 接口的引用。 真正的类型可能是 TypeVariableGenericArrayTypeParameterizedTypeClass 或其他我目前不知道的实例。

需要不同的方法来检索字段的实际类型。

这是我以 TypeFieldTreeNode 对象树的形式获取公共字段信息的解决方案。

public class TypeFieldTreeNode 
    public String fieldName;
    public String typeSimpleName;
    public String typeCanonicalName;
    public String typeGenericName;
    public List<TypeFieldTreeNode> children;

    public TypeFieldTreeNode(String fieldName, String typeSimpleName, String typeCanonicalName, String genericTypeName) 
        this.fieldName = fieldName;
        this.typeSimpleName = typeSimpleName;
        this.typeCanonicalName = typeCanonicalName;
        this.typeGenericName = genericTypeName;
        this.children = new ArrayList<>();
    

主要方法:

private List<TypeFieldTreeNode> getTypeFields(Class<?> clazz, Type genericType,
                                              Map<TypeVariable<?>, Type> actualClassArguments) throws Exception 
    if(clazz == null)  
        return Collections.emptyList();
    

    List<Field> fields = Arrays.stream(clazz.getDeclaredFields())
            .filter(f -> Modifier.isPublic(f.getModifiers()) && !Modifier.isFinal(f.getModifiers()))
            .collect(Collectors.toList());

    List<TypeFieldTreeNode> result = new ArrayList<>();
    Map<TypeVariable<?>, Type> classArgumentsMap = mapTypeActualClassArguments(
            clazz, genericType, actualClassArguments);

    for(Field field : fields) 
        result.add(getClassFieldData(field, classArgumentsMap));
    

    if(clazz.getSuperclass() != null) 
        List<TypeFieldTreeNode> superClassFields =
                getTypeFields(clazz.getSuperclass(), clazz.getGenericSuperclass(), classArgumentsMap);
        result.addAll(superClassFields);
    
    return result;

接下来是一个核心方法的列表,它将泛型参数的 TypeVariable 类型的元数据与泛型参数的实际类型绑定。 当泛型参数的类型是 TypeVariable 的实例时,方法使用前面获得的映射来恢复该泛型参数的实际类型:

private Map<TypeVariable<?>, Type> mapTypeActualClassArguments(Class<?> clazz, Type genericType,
                                                                   Map<TypeVariable<?>, Type> actualClassArguments) throws Exception 
        if(!(genericType instanceof ParameterizedType)) 
            return Collections.emptyMap();
        

        Map<TypeVariable<?>, Type> result = new HashMap<>();
        Type[] actualTypeParametersTypes = ((ParameterizedType) genericType).getActualTypeArguments();
        TypeVariable<?>[] classTypeParameters = clazz.getTypeParameters();

        for (int i = 0; i < classTypeParameters.length; i++) 
            if(actualTypeParametersTypes[i] instanceof TypeVariable<?>) 
                TypeVariable<?> fieldTypeVariable = (TypeVariable<?>) actualTypeParametersTypes[i];

                if(actualClassArguments.containsKey(fieldTypeVariable))
                    actualTypeParametersTypes[i] = actualClassArguments.get(fieldTypeVariable);
                else
                    throw new Exception(String.format("For generic parameter %s of type %s, the corresponding actual type of generic parameter was not found",
                            classTypeParameters[i].getName(), genericType.getTypeName()));
            
            result.put(classTypeParameters[i], actualTypeParametersTypes[i]);
        

        return result;
    

获取有关字段的数据以及属于该字段类型的类的所有可用字段:

private TypeFieldTreeNode getClassFieldData(Field field, 
                                            Map<TypeVariable<?>, Type> actualClassArguments) throws Exception 
    Class<?> fieldClass = field.getType();
    Type fieldGenericType = field.getGenericType();
    TypeFieldTreeNode result = null;

    // if type of the field is a generic parameter of the class containing the field
    if(fieldGenericType instanceof TypeVariable<?>) 
        Type actualFieldType = null;
        Class<?> actualFieldClass = null;
        Map<TypeVariable<?>, Type> fieldTypeActualClassArguments = new HashMap<>();
        TypeVariable<?> fieldTypeVariable = (TypeVariable<?>) fieldGenericType;

        if(actualClassArguments.containsKey(fieldTypeVariable))
            actualFieldType = actualClassArguments.get(fieldTypeVariable);
        else
            throw new Exception(String.format("For a field %s of type %s from class %s, the corresponding actual type of generic parameter was not found",
                    field.getName(), fieldGenericType.getTypeName(), field.getDeclaringClass().getCanonicalName()));

        // for example, field "myField2" of class MyClass2<MyClass<Integer>> where:
        // public class MyClass2<T>  public T myField2; 
        // public class MyClass<T>  public T myField; 
        if(actualFieldType instanceof ParameterizedType) 
            actualFieldClass = (Class<?>)((ParameterizedType) actualFieldType).getRawType();
            result = new TypeFieldTreeNode(field.getName(), actualFieldClass.getSimpleName(),
                    actualFieldClass.getCanonicalName(), actualFieldType.getTypeName());

            fieldTypeActualClassArguments = mapTypeActualClassArguments(actualFieldClass, actualFieldType, actualClassArguments);
        
        // for example, field "myField" of class MyClass<Integer> where:
        // public class MyClass<T>  public T myField; 
        else 
            actualFieldClass = (Class<?>) actualFieldType;
            result = new TypeFieldTreeNode(field.getName(), actualFieldClass.getSimpleName(),
                    actualFieldClass.getCanonicalName(), "");
        

        List<Field> childFields = Arrays.stream(actualFieldClass.getFields())
                .filter(f -> !Modifier.isFinal(f.getModifiers()))
                .collect(Collectors.toList());
        for (Field childField : childFields) 
            result.children.add(getClassFieldData(childField, fieldTypeActualClassArguments));
        
    
    // if the field is an array and the type of the elements of the array is a generic parameter of the class containing the field
    // for example, field "myField" of class MyClass<Integer> where:
    // public class MyClass<T>  public T[] myField; 
    else if(fieldGenericType instanceof GenericArrayType) 
        Type genericComponentType = ((GenericArrayType) fieldGenericType).getGenericComponentType();
        if(genericComponentType instanceof TypeVariable<?>) 
            if(actualClassArguments.containsKey(genericComponentType)) 
                Type actualArrayComponentType = actualClassArguments.get(genericComponentType);
                assert !(actualArrayComponentType instanceof ParameterizedType);
                Class<?> actualArrayClass = (Class<?>) actualArrayComponentType;
                result = new TypeFieldTreeNode(field.getName(), actualArrayClass.getSimpleName() + "[]",
                        actualArrayClass.getCanonicalName() + "[]", "");
            
            else
                throw new Exception(String.format("For a field %s of type %s from class %s, the corresponding actual type of generic parameter was not found",
                        field.getName(), fieldGenericType.getTypeName(), field.getDeclaringClass().getCanonicalName()));
        
        else
            throw new Exception(String.format("Unknown array genericComponentType: %s", genericComponentType.getClass().getCanonicalName()));
    
    else 
        result = new TypeFieldTreeNode(field.getName(), fieldClass.getSimpleName(), fieldClass.getCanonicalName(), "");
        Map<TypeVariable<?>, Type> fieldTypeActualClassArguments = new HashMap<>();

        // for example, field "myField2" of class MyClass2<Integer> where:
        // public class MyClass2<T>  public MyClass<T> myField2; 
        // public class MyClass<T>  public T myField; 
        if(fieldGenericType instanceof ParameterizedType) 

            // custom generic type name creator for situations when actual type arguments can be of type TypeVariable
            result.typeGenericName = getGenericTypeName((ParameterizedType)fieldGenericType, actualClassArguments);
            fieldTypeActualClassArguments = mapTypeActualClassArguments(fieldClass, fieldGenericType, actualClassArguments);
        

        List<Field> childFields = Arrays.stream(fieldClass.getFields()).filter(f -> !Modifier.isFinal(f.getModifiers()))
                .collect(Collectors.toList());
        for (Field childField : childFields) 
            result.children.add(getClassFieldData(childField, fieldTypeActualClassArguments));
        
    

    return result;


private String getGenericTypeName(ParameterizedType parameterizedType, 
                                  Map<TypeVariable<?>, Type> actualClassArguments) throws Exception  
    List<String> genericParamJavaTypes = new ArrayList<>();
    for(Type typeArgument : parameterizedType.getActualTypeArguments()) 
        if (typeArgument instanceof TypeVariable<?>) 
            TypeVariable<?> typeVariable = (TypeVariable<?>) typeArgument;
            if(actualClassArguments.containsKey(typeVariable)) 
                typeArgument = actualClassArguments.get(typeVariable);
             else
                throw new Exception(String.format("For generic parameter %s of type %s, the corresponding actual type of generic parameter was not found",
                        typeArgument.getTypeName(), parameterizedType.getTypeName()));
        

        if(typeArgument instanceof ParameterizedType) 
            ParameterizedType parameterizedTypeArgument = (ParameterizedType) typeArgument;
            Map<TypeVariable<?>, Type> typeActualClassArguments = mapTypeActualClassArguments(
                    (Class<?>)parameterizedTypeArgument.getRawType(),
                    typeArgument, actualClassArguments);
            genericParamJavaTypes.add(getGenericTypeName((ParameterizedType) typeArgument, typeActualClassArguments));
        
        else if (typeArgument instanceof Class<?>)
            genericParamJavaTypes.add(((Class<?>) typeArgument).getCanonicalName());
        else
            throw new Exception(String.format("For generic parameter %s of type %s, the corresponding actual type of generic parameter was not found", typeArgument.getTypeName()));
    

    Class<?> rawType = (Class<?>) parameterizedType.getRawType();
    return rawType.getCanonicalName() + "<" + String.join(", ", genericParamJavaTypes) + ">";

用法:

public List<TypeFieldTreeNode> getReturnTypeFields(Method method) throws Exception 
    return getTypeFields(method.getReturnType(),
            method.getGenericReturnType(), Collections.emptyMap());

对于以下测试类型,该解决方案按预期工作:

MyClass2&lt;MyClass&lt;Integer&gt;, MyClass&lt;Boolean&gt;, Double&gt; MyClass3&lt;MyClass&lt;Integer&gt;, MyClass&lt;Double&gt;&gt;

地点:

public class MyClass<T> 
    public T value;
    public List<String> list;


public class MyClass2<T, V, E> 
    public T value;
    public List<String> strList;
    public List<V> genericList;
    public int[] intArray;
    public E[] genericArray;
    public MyClass<E> genericClass;


public class MyClass3<T, V> extends MyClass2<T, V, Boolean> 
    public T value3;
    public List<V> genericList3;

【讨论】:

【参考方案8】:
public static Type[] getGenericTypes(Field field) 
    ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
    Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
    return actualTypeArguments;


class User 
    ...
    private Set<Authority> authorities = new HashSet<>();
    ...


/// usage
Class c = User.class;
Field field = c.getDeclaredField("authorities");
Type[] types = getGenericTypes(field);
log.info("Types: ", types); 
/// result
Types: class com.fajar.medicalinventory.entity.Authority

【讨论】:

以上是关于如何确定 Java 中泛型字段的类型?的主要内容,如果未能解决你的问题,请参考以下文章

Java基础Java中如何获取一个类中泛型的实际类型

java泛型中的下限

Java中泛型 类型擦除

Java中泛型中的几个符号

Java中如何获取一个类中泛型的实际类型

java中泛型 T 与 ? 的区别