Java中的协方差 - 无法添加收集

时间:2017-06-29 08:02:08

标签: java covariance

我正在阅读一篇关于java中协方差的有趣的dzone文章,这很容易理解,但有一件事让我觉得不合理,文章就在这里https://dzone.com/articles/covariance-and-contravariance

我引用了这篇文章的例子,它解释了为什么不能将一个集合添加到:

使用协方差,我们可以从结构中读取项目,但我们不能在其中写入任何内容。所有这些都是有效的协变声明。

List<? extends Number> myNums = new ArrayList<Integer>();

因为我们可以确定无论实际列表包含什么,它都可以被上传到一个数字(毕竟任何扩展数字的数字都是数字,对吧?) 但是,我们不允许将任何内容放入协变结构中。

myNumst.add(45); //compiler error

这是不允许的,因为编译器无法确定通用结构中对象的实际类型。它可以是扩展Number的任何东西(如Integer,Double,Long),但编译器无法确定

上面的段落对我来说没有意义,编译器知道列表包含Number或任何扩展它的内容,ArrayList被输入Integer。编译器知道插入的文字int。

那么为什么它会强制执行,因为在我看来它可以确定类型?

3 个答案:

答案 0 :(得分:5)

当编译器出现时:

List<? extendsNumber> myNums = new ArrayList<Integer>();

它会检查左侧和右侧的类型是否合在一起。 但编译器&#34;记得&#34;用于右侧实例化的特定类型。

可以说编译器会记住这个myNums

  • 已初始化
  • 类型为List<? extendsNumber>

是编译器可以不断折叠;和数据流分析;并且可能可以让编译器跟踪&#34;实例化类型&#34;信息 - 但唉:java编译器不这样做。

它只知道myNums已初始化List<? extends Numbers> - 仅此而已。

答案 1 :(得分:4)

你缺少两点:

  • 您只考虑局部变量:

    public void myMethod() {
       List<? extends Number> list = new ArrayList<Integer>();
       list.add(25);
    }
    

    编译器可以很容易地检测到?的实际值,但我知道没有人会编写这样的代码;如果您要使用整数列表,则只需将变量声明为List<Integer>

    协方差和逆变在处理参数和/或结果时最有用;这个例子更现实:

    public List<Integer> convert(List<? extends Number> source) {
       List<Integer> target = new ArrayList<>();
       for (Number number : source) {
          target.add(number.intValue());
       }
       return target;
    }
    

    编译器如何知道哪个类型用于参数化列表?即使在编译时,所有调用都只传递ArrayList<Integer>的实例,稍后代码可以使用带有不同参数的方法而不重新编译类。

  •   

    上面的段落对我来说没有意义,编译器知道列表包含Number或任何扩展它的东西,并且ArrayList被键入Integer。编译器知道插入的文字int。

    不,编译器知道的是列表包含扩展Number某些(包括Number)。它无法判断它是List<Number>(在这种情况下您可以插入Number的任何实例)或List<Integer>(在这种情况下,您只能插入Integer个实例) 。但它知道您使用get检索的所有内容都是Number的实例(即使它不确定具体的类)。

答案 2 :(得分:4)

  

上面的段落对我来说没有意义,编译器知道列表包含Number或任何扩展它的东西,并且ArrayList被键入Integer。编译器知道插入的文字int。

考虑一个略有不同但相关的例子:

Object obj = "";

通过上面的论证,编译器也应该能够知道obj实际上是String,因此您可以调用String - 具体方法在它上面:

obj.substring(0);

任何有一点Java经验的人都知道你不能。

(以及您)(或者,至少是编写代码的人)具有类型信息,并且已经做出了故意做出的丢弃决定它:没有理由将变量类型声明为Object,那么为什么编译器必须投入工作来尝试恢复该信息呢? (*)

出于同样的原因,如果您希望编译器知道变量的值

List<? extends Number> myNums = new ArrayList<Integer>();

ArrayList<Integer>,声明该变量属于该类型。否则,对于编译器而言,它更简单地假设&#34;这绝对是类型范围内的任何内容&#34;。

(*)我在SO的某个地方的某个地方读到了这个论点。我不记得它在哪里,或者我给出了适当的归属。