Java 为什么数组是协变的,而泛型是不变的?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/18666710/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why are arrays covariant but generics are invariant?
提问by eagertoLearn
From Effective Java by Joshua Bloch,
来自 Joshua Bloch 的 Effective Java,
- Arrays differ from generic type in two important ways. First arrays are covariant. Generics are invariant.
Covariant simply means if X is subtype of Y then X[] will also be sub type of Y[]. Arrays are covariant As string is subtype of Object So
String[] is subtype of Object[]
Invariant simply means irrespective of X being subtype of Y or not ,
List<X> will not be subType of List<Y>.
- 数组在两个重要方面与泛型类型不同。第一个数组是协变的。泛型是不变的。
协变只是意味着如果 X 是 Y 的子类型,那么 X[] 也将是 Y[] 的子类型。数组是协变的因为字符串是对象的子类型所以
String[] is subtype of Object[]
不变只是意味着不管 X 是否是 Y 的子类型,
List<X> will not be subType of List<Y>.
My question is why the decision to make arrays covariant in Java? There are other SO posts such as Why are Arrays invariant, but Lists covariant?, but they seem to be focussed on Scala and I am not able to follow.
我的问题是为什么决定在 Java 中使数组协变?还有其他 SO 帖子,例如为什么数组是不变的,但列表是协变的?,但他们似乎专注于 Scala,我无法跟上。
采纳答案by Paul Bellora
Via wikipedia:
通过维基百科:
Early versions of Java and C# did not include generics (a.k.a. parametric polymorphism).
In such a setting, making arrays invariant rules out useful polymorphic programs. For example, consider writing a function to shuffle an array, or a function that tests two arrays for equality using the
Object.equals
method on the elements. The implementation does not depend on the exact type of element stored in the array, so it should be possible to write a single function that works on all types of arrays. It is easy to implement functions of typeboolean equalArrays (Object[] a1, Object[] a2); void shuffleArray(Object[] a);
However, if array types were treated as invariant, it would only be possible to call these functions on an array of exactly the type
Object[]
. One could not, for example, shuffle an array of strings.Therefore, both Java and C# treat array types covariantly. For instance, in C#
string[]
is a subtype ofobject[]
, and in JavaString[]
is a subtype ofObject[]
.
Java 和 C# 的早期版本不包括泛型(又名参数多态)。
在这种情况下,使数组保持不变会排除有用的多态程序。例如,考虑编写一个函数来对数组进行混洗,或者编写一个使用
Object.equals
元素上的方法来测试两个数组是否相等的函数。该实现不依赖于存储在数组中元素的确切类型,因此应该可以编写一个适用于所有类型数组的函数。很容易实现类型的功能boolean equalArrays (Object[] a1, Object[] a2); void shuffleArray(Object[] a);
但是,如果数组类型被视为不变的,则只能在类型为 的数组上调用这些函数
Object[]
。例如,不能打乱字符串数组。因此,Java 和 C# 都以协变方式处理数组类型。例如,在 C# 中
string[]
是 的子类型object[]
,而在 Java 中String[]
是 的子类型Object[]
。
This answers the question "Why are arrays covariant?", or more accurately, "Why werearrays made covariant at the time?"
这回答了“为什么数组是协变的?”的问题,或者更准确地说,“为什么当时数组是协变的?”
When generics were introduced, they were purposefully not made covariant for reasons pointed out in this answer by Jon Skeet:
当引入泛型时,由于 Jon Skeet在这个答案中指出的原因,它们故意没有协变:
No, a
List<Dog>
is not aList<Animal>
. Consider what you can do with aList<Animal>
- you can add any animal to it... including a cat. Now, can you logically add a cat to a litter of puppies? Absolutely not.// 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?
Suddenly you have a veryconfused cat.
不,a
List<Dog>
不是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?
突然间你有一只非常困惑的猫。
The original motivation for making arrays covariant described in the wikipedia article didn't apply to generics because wildcardsmade the expression of covariance (and contravariance) possible, for example:
维基百科文章中描述的使数组协变的最初动机不适用于泛型,因为通配符使协变(和逆变)的表达成为可能,例如:
boolean equalLists(List<?> l1, List<?> l2);
void shuffleList(List<?> l);
回答by Rahul Tripathi
May be thishelp:-
可能是这个帮助:-
Generics are not covariant
泛型不是协变的
Arrays in the Java language are covariant -- which means that if Integer extends Number (which it does), then not only is an Integer also a Number, but an Integer[] is also a Number[]
, and you are free to pass or assign an Integer[]
where a Number[]
is called for. (More formally, if Number is a supertype of Integer, then Number[]
is a supertype of Integer[]
.) You might think the same is true of generic types as well -- that List<Number>
is a supertype of List<Integer>
, and that you can pass a List<Integer>
where a List<Number>
is expected. Unfortunately, it doesn't work that way.
Java 语言中的数组是协变的——这意味着如果 Integer 扩展了 Number(它确实如此),那么不仅一个 Integer 也是一个 Number,而且一个 Integer[] 也是一个Number[]
,你可以自由地传递或分配一个Integer[]
其中 aNumber[]
被调用。(更正式地说,如果 Number 是 Integer 的超类型,那么它Number[]
是 的超类型Integer[]
。)您可能认为泛型类型也是如此——它List<Number>
是 的超类型List<Integer>
,并且您可以List<Integer>
在需要a 的地方传递 a List<Number>
。不幸的是,它不能那样工作。
It turns out there's a good reason it doesn't work that way: It would break the type safety generics were supposed to provide. Imagine you could assign a List<Integer>
to a List<Number>
.
Then the following code would allow you to put something that wasn't an Integer into a List<Integer>
:
事实证明,它不能那样工作有一个很好的理由:它会破坏类型安全泛型应该提供的。想象一下,您可以将 a 分配List<Integer>
给 a List<Number>
。然后以下代码将允许您将不是 Integer 的内容放入 a List<Integer>
:
List<Integer> li = new ArrayList<Integer>();
List<Number> ln = li; // illegal
ln.add(new Float(3.1415));
Because ln is a List<Number>
, adding a Float to it seems perfectly legal. But if ln were aliased with li
, then it would break the type-safety promise implicit in the definition of li -- that it is a list of integers, which is why generic types cannot be covariant.
因为 ln 是 a List<Number>
,向它添加一个 Float 似乎是完全合法的。但是如果 ln 被别名为li
,那么它会破坏 li 定义中隐含的类型安全承诺——它是一个整数列表,这就是泛型类型不能协变的原因。
回答by Katona
The reason is that every array knows its element type during runtime, while generic collection doesn't because of type erasure.
原因是每个数组在运行时都知道它的元素类型,而泛型集合不知道是因为类型擦除。
For example:
例如:
String[] strings = new String[2];
Object[] objects = strings; // valid, String[] is Object[]
objects[0] = 12; // error, would cause java.lang.ArrayStoreException: java.lang.Integer during runtime
If this was allowed with generic collections:
如果泛型集合允许这样做:
List<String> strings = new ArrayList<String>();
List<Object> objects = strings; // let's say it is valid
objects.add(12); // invalid, Integer should not be put into List<String> but there is no information during runtime to catch this
But this would cause problems later when someone would try to access the list:
但这会在稍后有人尝试访问列表时导致问题:
String first = strings.get(0); // would cause ClassCastException, trying to assign 12 to String
回答by supercat
Arrays are covariant for at least two reasons:
数组是协变的,至少有两个原因:
It is useful for collections that hold information which will never change to be covariant. For a collection of T to be covariant, its backing store must also be covariant. While one could design an immutable
T
collection which did not use aT[]
as its backing store (e.g. using a tree or linked list), such a collection would be unlikely to perform as well as one backed by an array. One might argue that a better way to provide for covariant immutable collections would have been to define a "covariant immutable array" type they could use a backing store, but simply allowing array covariance was probably easier.Arrays will frequently be mutated by code which doesn't know what type of thing is going to be in them, but won't put into the array anything which wasn't read out of that same array. A prime example of this is sorting code. Conceptually it might have been possible for array types to include methods to swap or permute elements (such methods could be equally applicable to any array type), or define an "array manipulator" object which hold a reference to an array and one or more things that had been read from it, and could include methods to store previously-read items into the array from which they had come. If arrays were not covariant, user code would not be able to define such a type, but the runtime could have included some specialized methods.
这对于包含永远不会改变为协变的信息的集合很有用。对于要协变的 T 集合,其后备存储也必须是协变的。虽然可以设计一个
T
不使用 aT[]
作为其后备存储的不可变集合(例如使用树或链表),但这样的集合不太可能像由数组支持的集合那样执行。有人可能会争辩说,提供协变不可变集合的更好方法是定义一个“协变不可变数组”类型,他们可以使用后备存储,但简单地允许数组协变可能更容易。数组经常会被代码改变,这些代码不知道其中将包含什么类型的东西,但不会将任何未从同一数组中读出的内容放入数组中。这方面的一个主要例子是排序代码。从概念上讲,数组类型可能包含交换或置换元素的方法(此类方法同样适用于任何数组类型),或者定义一个“数组操纵器”对象,该对象保存对数组的引用和一个或多个事物已经从中读取的,并且可以包括将先前读取的项目存储到它们来自的数组中的方法。如果数组不是协变的,用户代码将无法定义这样的类型,但运行时可以包含一些专门的方法。
The fact that arrays are covariant may be viewed as an ugly hack, but in most cases it facilitates the creation of working code.
数组是协变的这一事实可能被视为一种丑陋的技巧,但在大多数情况下,它有助于创建工作代码。
回答by ajb
My take: When code is expecting an array A[] and you give it B[] where B is a subclass of A, there's only two things to worry about: what happens when you read an array element, and what happens if you write it. So it's not hard to write language rules to ensure that type safety is preserved in all cases (the main rule being that an ArrayStoreException
could be thrown if you try to stick an A into a B[]). For a generic, though, when you declare a class SomeClass<T>
, there can be any number of ways T
is used in the body of the class, and I'm guessing it's just way too complicated to work out all the possible combinations to write rules about when things are allowed and when they aren't.
我的看法:当代码需要一个数组 A[] 而你给它 B[] 其中 B 是 A 的子类时,只需要担心两件事:读取数组元素时会发生什么,以及如果你写会发生什么它。因此,编写语言规则以确保在所有情况下都保留类型安全并不难(主要规则是,ArrayStoreException
如果您尝试将 A 插入 B[] ,则可能会抛出 an)。但是,对于泛型,当您声明一个 class 时SomeClass<T>
,T
在该类的主体中可以使用多种方法,我猜想找出所有可能的组合来编写有关何时的规则太复杂了事情是允许的,什么时候不允许。
回答by meriton
An important feature of parametric types is the ability to write polymorphic algorithms, i.e. algorithms that operate on a data structure regardless of its parameter value, such as Arrays.sort()
.
参数类型的一个重要特性是能够编写多态算法,即对数据结构进行操作而不考虑其参数值的算法,例如Arrays.sort()
。
With generics, that's done with wildcard types:
对于泛型,这是通过通配符类型完成的:
<E extends Comparable<E>> void sort(E[]);
To be truly useful, wildcard types require wildcard capture, and that requires the notion of a type parameter. None of that was available at the time arrays were added to Java, and makings arrays of reference type covariant permitted a far simpler way to permit polymorphic algorithms:
为了真正有用,通配符类型需要通配符捕获,这需要类型参数的概念。在将数组添加到 Java 时,这些都不可用,并且引用类型协变数组允许以更简单的方式允许多态算法:
void sort(Comparable[]);
However, that simplicity opened a loophole in the static type system:
然而,这种简单性在静态类型系统中打开了一个漏洞:
String[] strings = {"hello"};
Object[] objects = strings;
objects[0] = 1; // throws ArrayStoreException
requiring a runtime check of every write access to an array of reference type.
需要对引用类型数组的每次写访问进行运行时检查。
In a nutshell, the newer approach embodied by generics makes the type system more complex, but also more statically type safe, while the older approach was simpler, and less statically type safe. The designers of the language opted for the simpler approach, having more important things to do than closing a small loophole in the type system that rarely causes problems. Later, when Java was established, and the pressing needs taken care of, they had the resources to do it right for generics (but changing it for arrays would have broken existing Java programs).
简而言之,泛型体现的新方法使类型系统更复杂,但也更静态类型安全,而旧方法更简单,静态类型安全性更低。该语言的设计者选择了更简单的方法,有比弥补类型系统中很少引起问题的小漏洞更重要的事情要做。后来,当 Java 建立并满足紧迫的需求时,他们有资源为泛型做正确的事情(但为数组更改它会破坏现有的 Java 程序)。
回答by alfasin
Generics are invariant: from JSL 4.10:
泛型是不变的:从JSL 4.10 开始:
...Subtyping does not extend through generic types: T <: U does not imply that
C<T>
<:C<U>
...
...子类型不通过泛型类型扩展: T <: U 并不意味着
C<T>
<:C<U>
...
and a few lines further, JLS also explains that
Arrays are covariant(first bullet):
还有几行,JLS 还解释了
数组是协变的(第一个项目符号):
4.10.3 Subtyping among Array Types
4.10.3 数组类型之间的子类型化
回答by Reza
I think they made a wrong decision at the first place that made array covariant. It breaks the type safety as it described hereand they got stuck with that because of backward compatibility and after that they tried to not make the same mistake for generic. And that's one of the reasons that Joshua Blochprefers lists to arra ys in Item 25 of book "Effective Java(second edition)"
我认为他们首先做出了错误的决定,导致数组协变。它打破了这里描述的类型安全性,由于向后兼容,他们陷入了困境,之后他们试图避免对泛型犯同样的错误。这就是Joshua Bloch在“Effective Java(第二版)”一书的第 25 条中更喜欢列表而不是数组的原因之一