获取父类(接口)的泛型信息

内容分享4天前发布
1 1 0

getGenericSuperclass() 和 getGenericInterfaces() 是 Java 反射中两个超级重大的方法,定义在 java.lang.Class<T> 类中。

它们的作用是:获取类的父类和接口信息,并且支持泛型(即返回 Type 而不是简单的 Class),这是与 getSuperclass() 和 getInterfaces() 的最大区别。


一、核心对比表

方法

返回类型

是否支持泛型

用途

getSuperclass()

Class<? super T>

❌ 不支持(擦除后类型)

获取原始父类(无泛型信息)

getGenericSuperclass()

Type

✅ 支持

获取带泛型的父类(如 List<String>)

getInterfaces()

Class<?>[]

❌ 不支持

获取实现的接口(仅原始类型)

getGenericInterfaces()

Type[]

✅ 支持

获取带泛型的接口(如 Comparable<Integer>)


二、getGenericSuperclass()

定义:

public Type getGenericSuperclass()

返回该类所继承的父类的泛型类型信息,包括实际类型参数。

如果当前类没有父类(如 Object 或 interface),则返回 null。


示例:带泛型的父类

// 基类:泛型 DAO
class Dao<T, ID> {
    T findById(ID id) { ... }
}

// 子类:具体化泛型
class User { }
class UserDao extends Dao<User, Long> {
}
Class<UserDao> clazz = UserDao.class;

// ❌ 普通方式:得不到泛型信息
Class<?> superClass = clazz.getSuperclass();
System.out.println(superClass); 
// 输出: class Dao → 没有 T 和 ID 的具体类型!

// ✅ 使用 getGenericSuperclass():可以拿到泛型信息
Type genericSuperclass = clazz.getGenericSuperclass();
if (genericSuperclass instanceof ParameterizedType) {
    ParameterizedType pt = (ParameterizedType) genericSuperclass;
    
    System.out.println("原始类型: " + pt.getRawType()); // class Dao
    
    Type[] actualTypes = pt.getActualTypeArguments();
    System.out.println("T = " + actualTypes[0]); // class User
    System.out.println("ID = " + actualTypes[1]); // class java.lang.Long
}

注意事项

  • 如果父类没有泛型(如 extends ArrayList),返回的是 Class<ArrayList>。
  • 如果是接口或 Object.class,返回 null。
  • 仅适用于直接父类,不递归查找。

三、getGenericInterfaces()

定义:

public Type[] getGenericInterfaces()

返回该类或接口所实现/扩展的所有接口的泛型类型信息


示例:实现带泛型的接口

// 泛型接口
interface Comparable<T> {
    int compareTo(T other);
}

// 具体实现
class Person implements Comparable<Person> {
    private String name;

    public int compareTo(Person other) {
        return this.name.compareTo(other.name);
    }
}
Class<Person> clazz = Person.class;

// ❌ getInterfaces():只返回原始类型
Class<?>[] interfaces = clazz.getInterfaces();
System.out.println(interfaces[0]); 
// 输出: interface Comparable → 没有泛型!

// ✅ getGenericInterfaces():返回 ParameterizedType
Type[] genericInterfaces = clazz.getGenericInterfaces();

if (genericInterfaces[0] instanceof ParameterizedType) {
    ParameterizedType pt = (ParameterizedType) genericInterfaces[0];
    System.out.println("接口: " + pt.getRawType()); // interface Comparable
    System.out.println("泛型参数: " + pt.getActualTypeArguments()[0]); // class Person
}

更复杂示例:多个泛型接口

class DataProcessor 
    implements Map<String, Integer>, 
               Comparable<List<String>> 
{
}
Type[] types = DataProcessor.class.getGenericInterfaces();

for (Type type : types) {
    if (type instanceof ParameterizedType) {
        ParameterizedType pt = (ParameterizedType) type;
        System.out.println(pt); 
        // java.util.Map<java.lang.String, java.lang.Integer>
        // java.lang.Comparable<java.util.List<java.lang.String>>
    }
}

四、典型应用场景

1. ORM 框架自动映射主键类型

// 根据 Dao<User, Long> 自动知道 ID 是 Long 类型
Type type = daoClass.getGenericSuperclass();
if (type instanceof ParameterizedType) {
    Class<?> idType = (Class<?>) pt.getActualTypeArguments()[1];
    // 构建主键生成策略...
}

2. JSON 反序列化泛型对象

// Jackson 中常用技巧
new TypeReference<Map<String, List<Integer>>>() {}
    .getType(); // 内部就是通过 getGenericSuperclass() 获取父类泛型

3. Spring Data JPA / MyBatis-Plus

  • 继承 JpaRepository<T, ID> 后,框架通过 getGenericSuperclass() 推断实体类型和主键类型。

4. 通用 CRUD 工具类

public abstract class BaseService<T> {
    private Class<T> entityClass;

    public BaseService() {
        ParameterizedType pt = (ParameterizedType) getClass().getGenericSuperclass();
        this.entityClass = (Class<T>) pt.getActualTypeArguments()[0];
    }
}

五、无法获取运行时动态泛型

示例说明

public class Example {
    // ✅ 字段:泛型信息可被反射获取(由于写在类结构里)
    private List<String> names;

    public void method() {
        // ❌ 局部变量:泛型信息仅存在于编译期
        List<Integer> numbers = new ArrayList<>();
        
        // 运行时,JVM 中这个变量就是 ArrayList,没有 Integer 信息
    }
}

哪些地方的泛型可以被获取?

这些位置的泛型信息会保留在 .class 文件的 签名(Signature) 中,可通过反射访问:

位置

是否保留泛型

反射能否获取

示例

字段(Field)

✅ 是

✅ 能

private List<String> list;

方法参数

✅ 是

✅ 能

void setItems(List<String> items)

方法返回值

✅ 是

✅ 能

List<User> getUsers()

父类(extends)

✅ 是

✅ 能

class MyList extends ArrayList<String>

实现接口(implements)

✅ 是

✅ 能

class Task implements Comparable<Task>

类本身的泛型参数

✅ 是

✅ 能

class Box<T> { T value; }


哪些地方的泛型无法获取?

这些位置的泛型信息 仅存在于编译期,运行时已被擦除:

位置

是否保留泛型

缘由

局部变量

❌ 否

编译后变为原始类型

new 对象时的泛型

❌ 否

new ArrayList<String>() → 运行时就是 ArrayList

方法体内泛型转换

❌ 否

(List<String>) list

强制类型转换由编译器插入,无反射信息

典型错误代码(无法工作)

public void badExample() {
    List<String> list = new ArrayList<>();
    
    // 想要通过 list 获取 String 类型?不行!
    Type type = list.getClass().getGenericSuperclass();
    // 得到的是:AbstractList<E>,E 是 TypeVariable,不是 String
    
    // list 本身是 ArrayList,不包含 String 信息
}

为什么局部变量泛型无法保留?

由于 Java 的 .class 文件中:

  • 字段、方法签名 有独立的 Signature 属性存储泛型信息。
  • 方法体内的字节码 只操作原始类型,泛型由编译器插入类型转换。

例如:

List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0);

编译后等价于:

ArrayList list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // 强制转型由编译器插入

→ 所以运行时根本没有 List<String> 这个“带泛型的类型”存在。


如何“绕过”限制?—— 使用 TypeToken技巧

虽然不能直接获取局部变量泛型,但我们可以通过 匿名内部类 + getGenericSuperclass() 来“固化”泛型信息。

这就是 Google Gson 和许多框架使用的 TypeToken 模式

// 利用匿名类继承带泛型的父类
Type type = new TypeReference<List<String>>() {}.getType();

// 内部实现原理
abstract class TypeReference<T> {
    private final Type type;
    
    protected TypeReference() {
        // 获取子类(匿名类)的父类泛型:List<String>
        ParameterizedType pt = (ParameterizedType) getClass().getGenericSuperclass();
        this.type = pt.getActualTypeArguments()[0];
    }
    
    public Type getType() {
        return type;
    }
}

这样就能“捕获”本应被擦除的泛型信息。

© 版权声明

相关文章

1 条评论

您必须登录才能参与评论!
立即登录
  • 头像
    小草莓爱睡觉_ 读者

    感谢分享👏

    无记录