为啥当“?扩展 Klass”被允许时,泛型被认为是不变的?

Posted

技术标签:

【中文标题】为啥当“?扩展 Klass”被允许时,泛型被认为是不变的?【英文标题】:Why are generics said to be invariant when "? extends Klass" is allowed?为什么当“?扩展 Klass”被允许时,泛型被认为是不变的? 【发布时间】:2015-03-14 02:04:03 【问题描述】:

在Java中,它说:

String[] is subtype of Object[]

所以说数组是协变的。但是对于泛型,他们说:

List<X> will not be subType of List<Y>.

因此它是不变的。但问题是,“泛型真的是不变的”吗?

例如,如果我给:

List<? extends Exception>

这意味着该列表可以采用Exception子类型,例如这是有效的:

List<? extends Exception> k = new ArrayList<NumberFormatException>();

那为什么说泛型是不变的呢?

【问题讨论】:

也许,因为 String 实际上是 Object 的子类型,但 X 可能是也可能不是 Y 的子类型,因为它是通用的......只是猜测 我的评论是对您问题的回答,而不是对why a close vote? :) 我在犹豫是否称其为重复,但我认为this question 和已接受答案中链接的 Java 教程很好地解释了这个问题。我认为造成混淆的主要原因是通配符限制了容器的类型,而不是容器元素的类型。 投票关闭的人不清楚您的要求不了解协变、不变和泛型,因此与其投票关闭,他们应该阅读这些主题并保持问题开放以供学习东西。 @LuiggiMendoza 请不要对人们的动机和理解做出假设。我是投票关闭为不清楚的人,并且(1)这个问题的原始版本以一个不完整的句子结尾,当时它没有被编辑; (2) 这个问题起初在我看来是要我们读懂别人的想法——即为什么人们称泛型为“不变的”,而它们的一种风格不是?所以也许我太快了,但你太快了,无法对“为什么”做出一些相当不准确的假设。 【参考方案1】:

List&lt;? extends Exception&gt; k = new ArrayList&lt;NumberFormatException&gt;();

这意味着列表可以采用 Exception 的子类型

不完全是。您可以分配kList——或其任何子类型,就像你有ArrayList——在这种情况下,Exception 的任何子类型。

但是你不能添加kException的任何子类型,或者任何与此相关的东西,因为k一些未知子类型List >Exception。例如,

k.add(new NumberFormatException());

会报错。

检索也仅限于已知类型:

NumberFormatException e1 = k.get(0); // error
Exception e2 = k.get(0); // ok, anything in k must be an Exception
NumberFormatException e3 = (NumberFormatException) k.get(0); // ok, but the usual downcast issues exist

【讨论】:

【参考方案2】:

数组在 java 中是协变的,但它们不应该是。这只是 Java 语言中的众多设计缺陷之一,尤其是打字系统。 考虑这段代码:

public void messUp(Object objects[])  objects[0] = "foo"; 
Integer ints[] = new Integer[] 1,2,3;
messUp(ints);

这会在没有警告的情况下编译,但在执行时会抛出 ArrayStoreException

为了回答您的问题,List&lt;T&gt; 是不变的,因为List&lt;String&gt; 不是List&lt;Object&gt; 的子类。 “extends”关键字用于约束类型参数,但不影响方差:&lt;T&gt; void foo(List&lt;T&gt;) 表示可以将任意类型的元素列表传递给foo&lt;T extends Exception&gt; void foo(List&lt;T&gt;)意思是一样的,只是它约束了类型参数T,所以它必须是Exception的子类。

这不会使List&lt;T&gt; 成为List&lt;Exception&gt; 的子类,它们仍然是两个不同的类。如果它是一个子类,你可以用它做与上面的数组相同的技巧:

<T extends Exception> void foo(List<T> exceptions)  
    List<Exception> l = exceptions;
    l.add(new RuntimeException());       

但这不会编译,因为List&lt;T&gt;不能赋值给List&lt;Exception&gt;(因为它不是子类);

【讨论】:

【参考方案3】:

我认为您问题的简单答案是语义问题。List&lt;Object&gt; 不是List&lt;String&gt; 的超类型。 Collection&lt;String&gt; 是它的超类型,而ArrayList&lt;String&gt; 是它可能的子类型之一。

换一种说法:

 Object[] array = new String[2]; //is a valid declaration.
 List<Object> list = new ArrayList<String>(); //is not. 

【讨论】:

以上是关于为啥当“?扩展 Klass”被允许时,泛型被认为是不变的?的主要内容,如果未能解决你的问题,请参考以下文章

java 泛型实现原理

java中的object类和泛型

为啥允许使用泛型 lambda 而不允许使用带有模板化方法的嵌套结构?

Java泛型

为啥 C# 允许将泛型数组与所有类型的模式进行匹配?

Java泛型的设计