什么是形变(Type Variance)?

在类型系统中,协变(covariance)逆变(contravariance) 统称为 型变(variance) 。它们描述的是在类型变换的作用下,子类型关系是否以及如何发生变化。

什么是型变(Variance)?

我们先看一个抽象模型:

  • 设有两个类型 XY,若 X ≤ Y 表示 XY 的子类型。
  • f 是一种 类型变换 ,即将类型映射为另一个复合类型,例如:f(T) 可以是 List<T>T[] 等。
  • 那么根据 f(X)f(Y) 的子类型关系,我们可以将 f 分为以下四类:
名称 表达式关系 含义
协变 X ≤ Yf(X) ≤ f(Y) 子类型关系保持不变
逆变 X ≤ Yf(Y) ≤ f(X) 子类型关系被反转
双变 X ≤ Yf(X) ≤ f(Y)f(Y) ≤ f(X) 双向成立,类型变换前后两者相互兼容
不可变 X ≤ Yf(X)f(Y)不可比较 类型变换后无子类型关系,类型之间完全独立

✅ 型变描述的是:子类型关系在类型变换之后是否还能“传递下去”。

示例说明:以 AnimalCat 为例

假设:

Cat  Animal

也就是说 CatAnimal 的子类。那么不同类型变换下构造出来的新类型关系可能如下:

1. 协变(Covariant) :类型关系保持

比如 Java 的数组类型:

Animal[] animals = new Cat[10]; // 合法

Cat[] ≤ Animal[],数组类型保持了原始的子类型关系 —— 说明 Java 中的数组是 协变的

还有Java 的泛型通配符 <? extends T>

List<? extends Animal> list = new ArrayList<Cat>();
Animal a = list.get(0); // ✅ 安全读取
list.add(new Dog());    // ❌ 编译错误,不能写入

此时,你不能往 list 中添加任何元素(除了 null),但可以安全地读取出 Animal 类型的对象。

这就是协变的典型特征:“只读” —— 你可以读取(out),但不能写入(in)。

2. 逆变(Contravariant) :类型关系反转

比如 Java 的泛型通配符 <? super T>

List<? super Cat> list = new ArrayList<Animal>(); // 合法
list.add(new Cat());     // ✅ 合法:写入 Cat
list.add(new BlackCat()); // ✅ 合法:BlackCat extends Cat

Object o = list.get(0);   // ✅ 合法,但只能是 Object 类型
Cat c = list.get(0);      // ❌ 编译错误:无法确保读出来的就是 Cat

虽然 Cat ≤ Animal,但 List<Animal> 却能赋值给 List<? super Cat>,说明泛型的 super 位置是 逆变的 。此时,你可以往list写入,但是不能读取。
这就是协变的典型特征:“只写” —— 你可以写入(out),但不能读取(in)。

3. 不可变(Invariant) :关系不传递

Java 中普通泛型默认是不可变的:

List<Cat> cats = new ArrayList<>();
List<Animal> animals = cats; // ❌ 编译错误

即使 Cat ≤ Animal,但 List<Cat>List<Animal> 没有任何子类型关系,说明泛型 List<T> 是默认 不可变的

4. 双变(Bivariant) :Java 不支持,但其他语言可能支持

在一些语言或场景下,某些类型可能既支持协变又支持逆变,比如某些函数类型或结构体字段类型,但 Java 中几乎不支持双变(由于类型安全问题)。


小结

类型变换f 举例(以 Java 为例) 所属变型类型
T[] 数组类型 协变
List 泛型的extends 协变
List 泛型的super 逆变
List 泛型默认 不可变
多数函数类型参数 & 返回值(其他语言) 双变(视情况而定)

标题:什么是形变(Type Variance)?
作者:Gakkiyomi
地址:https://gakkiyomi.blog/articles/2025/07/19/1752897315152.html

    评论
    1 评论
    咕咕咕 2025-08-05 13:07

    哇咔咔

avatar

取消