posts

Oct 19, 2020

🏎协变、逆变与不变

Estimated Reading Time: 2 minutes (322 words)

Formal definition

摘自Covariance_and_contravariance_(computer_science)

Within the type system of a programming language, a typing rule or a type constructor is:

协变

首先考虑数组类型构造器: 从Animal类型,可以得到Animal[](“animal数组”)。 是否可以把它当作

Java中的协变数组

// a 是单元素的 String 数组
String[] a = new String[1];

// b 是 Object 的数组
Object[] b = a;

// 向 b 中赋一个整数。如果 b 确实是 Object 的数组,这是可能的;然而它其实是个 String 的数组,因此会发生 java.lang.ArrayStoreException
b[0] = 1;

OOP中的继承

当一个子类重写一个超类的方法时,编译器必须检查重写方法是否具有正确的类型。虽然一些语言要求类型必须与超类相同,但允许重写方法有一个“更好的”类型也是类型安全的。对于大部分的方法子类化规则来说,这要求返回值的类型必须更具体,也就是协变,而且接受更宽泛的参数类型,也就是逆变

be914bff8d5611a54aa47597.png

以下讨论基于Java语法,抽象类C如下:

jshell> abstract class C {
   ...> abstract C foo(C c);
   ...> }
|  replaced class C

返回值的协变

在允许协变返回值的语言中, 子类可以重写foo方法返回一个更具体的类型:

jshell> class D extends C {
   ...> @Override
   ...> D foo(C c) {return this;}
   ...> }
|  created class D

方法参数的协变与逆变

允许参数协变、逆变的面向对象语言并不多——Java会把它当成一个函数重载:

协变:

jshell> class E extends C {
   ...> @Override
   ...> C foo(E e) {return this;}
   ...> }
|  Error:
|  E is not abstract and does not override abstract method foo(C) in C
|  class E extends C {
|  ^------------------...
|  Error:
|  method does not override or implement a method from a supertype
|  @Override
|  ^-------^

逆变:

jshell> class F extends C {
   ...> @Override
   ...> C foo(Object o) {return this;}
   ...> }
|  Error:
|  F is not abstract and does not override abstract method foo(C) in C
|  class F extends C {
|  ^------------------...
|  Error:
|  method does not override or implement a method from a supertype
|  @Override
|  ^-------^

Summary of variance and inheritance

语言Parameter typeReturn type
C++ (自1998年), Java (自J2SE 5.0), D不变协变
C#不变不变

参考