【问题标题】:Java generics and inheritence confusionJava泛型和继承混淆
【发布时间】:2012-09-13 20:23:10
【问题描述】:

在 Kathy Sierra 关于 SCJP 的书中,我们了解到,当我们写作时

List <A> list = new ArrayList <A> ();

这意味着这个列表将只接受类型 A 的元素,而不是它的子类型或超类型,只接受类型 A。然后我遇到了这个例子

import java.util.*;

interface A {
    public void a();
}
class B implements A {
    public void a() { }
    public void b() { }
}

class C extends B {
    public void a() { }
}

class D extends C {

}

public class Generics {
    public static void main() {
        List <B> lst = new ArrayList <B> ();
        lst.add(new B());
        lst.add(new C());
        lst.add(new D());
    }
}

这里已经用 B 声明了一个列表作为它的绑定类型。但它看起来也接受它的子类型的对象。为什么会这样?如果可能,那么这个声明在 java 中是否可用

List <? extends B> list = new ArrayList <B> ();

现在我们用这个符号告诉编译器任何扩展 B 的类型都可以进入这个列表。请帮忙,我真的很困惑

【问题讨论】:

标签: java generics


【解决方案1】:

集合中允许的类型与集合本身的声明类型之间存在区别。对于

List <A> list = new ArrayList <A> ();

它声明了一个元素列表。每个元素 is-a A。每个元素都可以是A 的子类,但列表本身被视为As 的列表。

注意区别。香蕉是一种水果。但是香蕉的集合不是水果的集合(违反直觉)。否则,您可以获取香蕉集合,将其视为水果集合,然后添加一个苹果。

List <? extends B> list = new ArrayList <B> ();

表示列表可以是 B 的列表,也可以是 B 的子类型列表。例如以下都是有效的:

List <? extends B> list = new ArrayList <B> ();
List <? extends B> list = new ArrayList <C> ();
List <? extends B> list = new ArrayList <D> ();

【讨论】:

    【解决方案2】:

    用List例子教授泛型的问题是人们可以理解例子,但仍然不能理解规则。

    随着泛型的出现,编译器还没有接受过人类知识的指导,因此它不知道List&lt;A&gt;元素的有序序列(可能是@ 987654321@ 带有长而弯曲的象牙),因此它不能保证 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 中每个子类型都是其超类型的有效替代品,因此 RectangleList&lt;Shape&gt;add(Shape s) 的合法参数。

    这时人们会想“泛型,我终于找到你了!”。然后他启动了一个 IDE,输入

    List<Shape> shapes = new LinkedList<Rectangle>();
    

    并且编译器拒绝编译。 "- 这是怎么回事?我可以将矩形添加到形状列表中,但矩形列表不是形状列表?"。问题是人们从列表、形状和矩形的角度来思考,但编译器不知道这些。它看到了

    T reference = <expression returning type S>
    

    所以它想知道“-ST 类型的有效替代品吗?”。换个说法,T 是根据 Java 规则构造的类型层次结构中 S 的父级吗?因此,它仔细检查了它的旧 Java 书籍,发现不,X&lt;A&gt; 不是 X&lt;B&gt; 父级。非法代码。停下来。

    "- 但是矩形列表肯定包含形状!我刚刚编译了一个程序,将一堆矩形添加到List&lt;Shape&gt;!"。这对编译器没有任何意义,它不是一个聪明的程序——你看……它只能解释它几年前学到的那几条规则,甚至无法区分List和@ 987654340@。它只知道它的旧Java书,并且在书中明确指出并指出X&lt;A&gt;X&lt;B&gt;之间没有子类型关系,无论AB如何相互关联。 "- 如果只有泛型可以像数组一样实现...你,愚蠢的 Java 人..." 事实上:

    String[] strings = new String[10];
    Object[] objects = strings;
    objects[0] = new Rectangle(); // ArrayStoreException at runtime
    

    也许你现在明白了。毕竟,那些 Java 人并不那么愚蠢……至少,他们为自己的类型系统选择的那些花哨的规则是有目的的,至少他们这样做是出于实际的原因。他们制作了数组协变,但泛型不变。由于数组和列表的内容都可以更改(它们是可变的),这会将数组(但不是列表)暴露给上面看到的问题。例如,Scala 通过使列表协变但不可变(List[Integer] 可以分配给 List[Number],但它们是只读的)和数组可变但不变(你不能使用 @ 987654348@,其中需要Array[Number],但您可以修改其内容。

    最后,要将AB 之间的子类型关系移植到泛型世界,可以使用通配符?,但这对于一个全新的故事来说很重要。我只是预计在旧 Java 书中写到 List&lt;Rectangle&gt; 不会扩展 List&lt;Shape&gt;,但它是 List&lt;? extends Shape&gt; 的合法子代。

    【讨论】:

    • 好的,现在正如您所解释的,编译器在构造列表时会检查类型层次结构。所以它发现list&lt;Shape&gt; 不是list&lt;Rect&gt;。但是如果是Rect extends Shape,那么稍后我们可以将类型为Rect 的元素添加到该列表中。那为什么编译器不阻止我们呢?如list&lt;Shape&gt; = new Rect(); 表示形状列表可以有一个矩形,但形状列表不能是矩形列表?
    • 在评论中重写我的答案是没有意义的 :) 我认为如果您慢慢阅读 2-3 次,您最终会明白的。 A列表和B列表之间没有关系,这是一个规则。一条规则。一条规则 :) 正如我解释的那样,它有其基本原理,但这绝对是语言设计者强加的规则
    • 查看 ListOfShapes 的定义是我的示例,以了解为什么可以使用矩形类型的参数调用 add()。这里真的没有什么是泛型独有的,它是普通的旧 Java。永远记住,编译器不知道列表对人类意味着什么。它只是确保程序根据语言规则是合法的
    【解决方案3】:

    “这意味着这个列表将只接受类型 A 的元素,而不是它的子类型或超类型,只接受类型 A。然后我遇到了这个例子”

    这是错误的——它将接受任何派生类型。 Java 泛型也是在编译器中使用擦除实现的。

    和方法参数一样,接受B,应该接受B,C,D。

    还要注意,在运行时因为擦除没有类型检查所以:

    List <B> lst = new ArrayList <B> ();
    

    变成

    List <object> lst = new ArrayList <object> ();
    

    这可能会对您有所帮助:

    http://www.ibm.com/developerworks/java/library/j-jtp01255/index.html

    【讨论】:

      【解决方案4】:

      “这意味着这个列表将只接受类型 A 的元素,而不是它的子类型或超类型,只接受类型 A” - 这是不正确的。我可以重写你的代码如下:

      List <A> list = new ArrayList <A> ();
      B b = new B();
      B c = new C();
      B d = new D();
      list.add(b);
      list.add(c);
      list.add(d);
      

      当我们将变量 b、c 和 d 分配给 B、C 和 D 的实例时,它们都属于“B”类型。多态性?

      如果你看到,B、C 和 D 实现了 A,因此我可以将 B、C、D 的实例分配给 A。

      【讨论】:

        猜你喜欢
        • 2011-04-25
        • 2015-05-13
        • 1970-01-01
        • 2011-03-06
        • 2016-05-13
        • 1970-01-01
        • 1970-01-01
        • 2020-01-09
        • 1970-01-01
        相关资源
        最近更新 更多