用 List 示例教授泛型的问题是人们可以理解示例,但仍然不能理解规则。
随着泛型的出现,编译器没有被人类知识指导,所以它不知道 aList<A>
是一个有序的元素序列(它可能是一个长而弯曲的象牙的长鼻),因此它不能保证 List只接受类型的元素A
。编译器知道的所有内容都由以下定义提供
interface List<E> {
public void add(E);
public E get(int index);
}
这段代码没有定义单一类型:它定义了无限系列的类型:
interface ListOfStrings {
public void add(String s);
public String get(int index);
}
interface ListOfShapes {
public void add(Shape e);
public Shape get(int index);
}
取决于您如何实例化参数化类型。由于在 Java 中每个子类型都是其超类型的有效替代品,因此 aRectangle
是 a 的合法List<Shape>
参数add(Shape s)
。
这是一个人认为“泛型,我终于找到你了! ”的时候。然后他启动了一个 IDE,输入
List<Shape> shapes = new LinkedList<Rectangle>();
并且编译器拒绝编译。“-这是怎么回事?我可以将矩形添加到形状列表中,但矩形列表不是形状列表?” . 问题是人们从列表、形状和矩形的角度来思考,但编译器不知道这些。它看到
T reference = <expression returning type S>
所以它想知道“ - 是S
type 的有效替代品T
吗?” . 换句话说,类型层次结构中T
的父级是S
根据 Java 规则构造的吗?所以它仔细检查了它的旧 Java 书,发现没有,X<A>
不是X<B>
父级。非法代码。停止。
“-但是矩形列表肯定包含形状!我刚刚编译了一个程序,它在一个矩形中添加了一堆矩形List<Shape>
!” . 这对编译器来说没有任何意义,它不是一个聪明的程序——你看……它只能解释它几年前学到的那几条规则,甚至无法区分 aList
和 a之间的区别Mammoth
。它只知道它的旧 Java 书,并且在书中明确说明并指出 and之间没有子类型关系X<A>
,X<B>
无论A
and之间如何B
相互关联。“ - 如果只有泛型像数组一样实现......你,愚蠢的Java人......”事实上:
String[] strings = new String[10];
Object[] objects = strings;
objects[0] = new Rectangle(); // ArrayStoreException at runtime
也许你现在明白了。毕竟,那些 Java 人并没有那么愚蠢……至少,他们为自己的类型系统选择的那些花哨的规则是有目的的,至少他们这样做是出于实际原因。他们使数组是协变的,但泛型是不变的。由于数组和列表的内容都可以更改(它们是可变的),这会将数组(但不是列表)暴露给上面看到的问题。例如,Scala通过使列表协变但不可变(aList[Integer]
可以分配给 a List[Number]
,但它们是只读的)和数组可变但不变(你不能在需要 an 的Array[Integer]
地方使用 an Array[Number]
,但你可以修改)来实现类型安全他们的内容)。
最后,为了将子类型关系移植A
到B
泛型世界中,可以使用通配符?
,但这对于一个全新的故事来说很重要。我只是预计在旧的 Java 书中写的是List<Rectangle>
不扩展List<Shape>
的,但它是List<? extends Shape>
.