MeteorCat / Java泛型反射

Created Thu, 04 Jan 2024 16:22:14 +0800 Modified Wed, 29 Oct 2025 23:25:00 +0800
2115 Words

Java泛型反射

基本上所有现代化语言都有泛型实现, 最大的实现就是容器对象:

  • Set<T>
  • List<T>
  • Map<KEY,VALUE>

注入此类容器对象, 这种类型在 Java 当中虽然有 List<Object>List<T> 两种方式, 但是请注意在 Object 是全局对象类型!

(Object value = 100) 和 (int value = 100) 两者一个是对象类型, 一个是值类型需要区分出来, 后续编译器则是直接帮助值类型转化为对象类型.

如果没有泛型反射, 那么需要处理不同类型如:

  • List<String>
  • List<Integer>
  • List<Long>
  • …其他相关类型扩展

后续衍生的泛型就是为了这样处理从而生成模板代码:

/**
 * T 就是泛型类模板, 用于替换成自定义泛型, 也就是 MyList<String>/ MyList<Integer> 都能自动生成.
 */
public class MyList<T> {
    private T[] array;
    private int size;
}

这样就能直接通过 MyList<String> myList = new MyList<String>() 生成模板代码, 编写一次万能匹配, 可以通过编译器保证了类型安全: 这就是 泛型.

这种泛型推断可以方便快捷衍生出集成接口:

/**
 * 比较接口, 要求对数组实现
 * @param <T>
 */
public interface Comparable<T> {
    /**
     * 返回负数: 当前实例比参数o小
     * 返回0: 当前实例与参数o相等
     * 返回正数: 当前实例比参数o大
     */
    int compareTo(T o);
}

// 这样就能对自己的自定义类对象处理比较大小操作
public class SelfClass implements Comparable<SelfClass> {
    private int value;

    /**
     * 让其去比较两者
     * @param other 其他实例
     * @return int
     */
    public int compareTo(SelfClass other) {
        return this.value.compareTo(other.value);
    }
}

这样就能对类对象来做自定义泛型处理, 十分简单方便.

注意: 泛型类型不能用于静态方法, 也就是无法静态化构建出泛型类型, 但是以下代码可以通过:

public class Message<T> {
    private final T msg;

    public Message(T msg) {
        this.msg = msg;
    }

    public T1 getMsg() {
        return msg;
    }

    // 这里是可以通过的
    public static <T> Message<T> build(T msg) {
        return new Message<>(msg);
    }
}

这是因为 <T>Message<T> 不算是同个类型, <T> 实际上算是 T2泛型类型, 用代码声明就类似:

public class Message<T1> {
    private final T1 msg;

    public Message(T1 msg) {
        this.msg = msg;
    }

    public T1 getMsg() {
        return msg;
    }

    // 实际上这里是又声明泛型类型T2, 和 T1 的泛型类型没有关系.
    public static <T2> Message<T2> build(T2 msg) {
        return new Message<>(msg);
    }
}

对于 Java 当中的泛型, 默认采用 Object 安装转化:

// 以上面的类型声明实例, 我们看到代码如下:
Message<String> msg = new Message<>("hello.world");
String info = msg.getMsg();

// 但是虚拟机处理却是以下情况, 虚拟机并没有泛型概念:
Message msg = new Message("hello.world");
// 默认内部泛型类型最会归为 Object 对象
// 只是取出来的时候被内部做强制Object转化而已
String info = (String)msg.getMsg();

这里也就衍生出 Java 内部泛型的 类型擦除, 也就是虚拟机在构建程序时候会把泛型类转化为全局 Object 后转化为指定类型:

  • 不能为值类型/基本类型: Message<int> msg = new Message<>(123); 这是错误的, 因为内部必须是最高 Object 对象衍生.
  • 无法获取泛型的 Class 分类: Message<Integer> == Message<String> == Message.class == Message<Object>.class 默认都是归属同个 Message<Object>.class
  • 无法判断泛型实力的类型: Message<Integer> msg = new Message<>(123); 该实例无法通过 if(msg instanceof Pair<String>) 识别是否是指定类型.
  • 无法 new 实例化泛型: T t1 = new T() 这种方式是错误的, 因为被内部擦除之后变成代码为 Object t1 = new Object(), 这种代码明显是无法通过执行的.
  • 注意异常覆写方式: equals(T t) 这种方式和 equals(Object t) 不能被声明, 因为 Object 默认已经涵盖这个方法, 泛型无法做重新覆盖处理.

当然在 Java 泛型是允许被衍生声明的只允许指定实现类的:

// 声明主要的泛型类
public class Message<T> {
    public T msg;
}

// 衍生出只要 String 的具体类
public class StringMessage extends Message<String> {
}

// 衍生出只要 Integer 的具体类
public class IntegerMessage extends Message<Integer> {

}

public class Executor {

    // 限定要求必须是 String 类型的泛型实例
    public static void strMsg(Message<? extends String> msg) {
        String ctx = msg.msg;
    }
}

这种 <?> 语法默认是函数方法泛型声明语法, 用于标识运行时才可知类型, 同时也可以要求指定某些实现 <? extends String> 要求类型必须是继承了 String 对象或者 String 本身:

// 表面上代码处理
void strMsg(Message<? extends String> msg);

// 实际上虚拟机擦除类型之后, 哪怕是 String 类型也因为里氏替换原则被父类捕获到
void strMsg(Message<String> msg);

除了 <?><? extends T> 之外, 还有 <? super T> 向上取类型方式:

// 这里传入的 msg 限定了传入类型必须是继承过 Integer 的类对象
// 这时候是处于向下获取子类的检索类型
void intMsg(Message<? extends Integer> msg);

// 但是 Integer 之上还有父类 Number, 现在要求不是向下类型检索, 而是向上
// 也就是要求传入 Integer 往上类型而非往下泛型
void setMsg(Message<? super Integer> msg);

// 对于虚拟机来说, 擦除类型之后函数方法签名为:
void setMsg(? super Integer);

// 所以需要传入的修改类型, 是可以被接受的, 因为 Number 类型就是 Integer 的上级对象.
Number value = value;
setMsg(value);

注意: <? extends T> 只能使用在被读取而不能被写入情况; 而 <? super T> 只能用于允许写入而不能读取情况.

  • <? extends T> 允许调用读方法 T get() 获取 T 的引用, 但不允许调用写方法 set(T) 传入 T的引用(传入null除外)
  • <? super T> 允许调用写方法 set(T) 传入 T 的引用, 但不允许调用读方法 T get() 获取 T 的引用(获取Object除外)

Java标准库实现就能看到具体实践:

public class Collections {
    // 把src的每个元素复制到dest中, 注意两个 extend 和 super 的使用
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        for (int i = 0; i < src.size(); i++) {
            T t = src.get(i);
            dest.add(t);
        }
    }
}

要关键留意: extends 负责读取, super 只允许写入

反射

Java 内部泛型衍生以下具体类系统结构:

  • Class
  • ParameterizedType
  • GenericArrayType
  • WildcardType

在默认获取对象类型来说:

// 获取类泛型, Class<T> 就是泛型
Class<String> clazz = String.class;

// 这里就相当于实例化, new 无法直接实例化, 只能依靠这样直接替代实例化
String instance = clazz.newInstance();

// 调用Class的 getSuperclass() 方法返回的Class类型是 Class<? super T>, 也就是可写入对象
Class<? super String> sup = String.class.getSuperclass();

// 完整生成链
Class<Integer> clazz = Integer.class;
Constructor<Integer> cons = clazz.getConstructor(int.class);
Integer i = cons.newInstance(123);

注意: 新版本 Java 安全升级之后 Class.newInstance() 这个实例化版本已经淘汰, 需要升级:

// 老版本
Class<String> clazz = String.class;
String instance = clazz.newInstance();

// 新版本, 要求 getConstructor 设置获取构造器之后实例化处理
Class<String> clazz = String.class;
Constructor<?> constructor = clazz.getConstructor(String.class);
Object instance = constructor.newInstance("Hello, world!");

同时需要注意, 不允许以 new 生成带泛型的数组:

// 这里是成功的, 因为类型擦除时候为 Message<Object>[] arr = null
Message<String>[] arr = null

// 但是直接 new 是直接错误的, 以下代码直接编写会错误
Message<String>[] arr = new Message<>[2];
// 擦除类型, 之后 Object 并不清除什么类型需要怎么分配, 直接就会异常
Message<Object>[] arr = new Message<Object>[2];

// 可以通过 @SuppressWarnings("unchecked") + 强制转化来绕过安全检查
Message<String>[] arr = (Message<String>[])new Message[2];

泛型数组处理要极其小心, 很容易会出现对象数据内存越界的情况, 特别是 千万不要把内部创建泛型数组传递给外部使用.