来自 Joshua Bloch 的 Effective Java,
String[] is subtype of Object[]
不变只是意味着不管 X 是否是 Y 的子类型,
List<X> will not be subType of List<Y>.
我的问题是为什么决定在 Java 中使数组协变?还有其他 SO 帖子,例如Why are Arrays invariant, but Lists covariant? ,但他们似乎专注于 Scala,我无法遵循。
通过维基百科:
Java 和 C# 的早期版本不包括泛型(也称为参数多态)。 在这种情况下,使数组保持不变会排除有用的多态程序。例如,考虑编写一个函数来对数组进行洗牌,或者编写一个使用Object.equals元素上的方法测试两个数组是否相等的函数。该实现不依赖于存储在数组中的元素的确切类型,因此应该可以编写一个适用于所有类型数组的函数。很容易实现类型的功能 boolean equalArrays (Object[] a1, Object[] a2); void shuffleArray(Object[] a); 但是,如果数组类型被视为不变量,则只能在类型为 的数组上调用这些函数Object[]。例如,不能对字符串数组进行洗牌。 因此,Java 和 C# 都以协变方式处理数组类型。例如,在 C#string[]中是 的子类型object[],而在 JavaString[]中是 的子类型Object[]。
Java 和 C# 的早期版本不包括泛型(也称为参数多态)。
在这种情况下,使数组保持不变会排除有用的多态程序。例如,考虑编写一个函数来对数组进行洗牌,或者编写一个使用Object.equals元素上的方法测试两个数组是否相等的函数。该实现不依赖于存储在数组中的元素的确切类型,因此应该可以编写一个适用于所有类型数组的函数。很容易实现类型的功能
Object.equals
boolean equalArrays (Object[] a1, Object[] a2); void shuffleArray(Object[] a);
但是,如果数组类型被视为不变量,则只能在类型为 的数组上调用这些函数Object[]。例如,不能对字符串数组进行洗牌。
Object[]
因此,Java 和 C# 都以协变方式处理数组类型。例如,在 C#string[]中是 的子类型object[],而在 JavaString[]中是 的子类型Object[]。
string[]
object[]
String[]
这回答了“为什么数组是协变的?”这个问题,或者更准确地说,“为什么 当时 数组是协变 的 ?”
当引入泛型时,由于 Jon Skeet在这个答案中指出的原因,故意不使它们成为协变的:
不, aList<Dog>不是 a List<Animal>。考虑一下你可以用 a 做什么List<Animal>- 你可以在其中添加任何动物......包括一只猫。现在,你能合乎逻辑地将一只猫添加到一窝小狗中吗?绝对不。 // Illegal code - because otherwise life would be Bad List<Dog> dogs = new List<Dog>(); List<Animal> animals = dogs; // Awooga awooga animals.add(new Cat()); Dog dog = dogs.get(0); // This should be safe, right? 突然你有一只 非常 困惑的猫。
不, aList<Dog>不是 a List<Animal>。考虑一下你可以用 a 做什么List<Animal>- 你可以在其中添加任何动物......包括一只猫。现在,你能合乎逻辑地将一只猫添加到一窝小狗中吗?绝对不。
List<Dog>
List<Animal>
// Illegal code - because otherwise life would be Bad List<Dog> dogs = new List<Dog>(); List<Animal> animals = dogs; // Awooga awooga animals.add(new Cat()); Dog dog = dogs.get(0); // This should be safe, right?
突然你有一只 非常 困惑的猫。
维基百科文章中描述的使数组协变的最初动机不适用于泛型,因为通配符使协变(和逆变)的表达成为可能,例如:
boolean equalLists(List<?> l1, List<?> l2); void shuffleList(List<?> l);