0

在 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 的类型都可以进入这个列表。请帮忙,我真的很困惑

4

4 回答 4

2

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

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

它声明了一个元素列表。每个元素都是-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> ();
于 2012-09-21T11:56:42.113 回答
1

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

随着泛型的出现,编译器没有被人类知识指导,所以它不知道 aList<A>一个有序的元素序列(它可能是一个长而弯曲的象牙的长鼻),因此它不能保证 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 中每个子类型都是其超类型的有效替代品,因此 aRectangle是 a 的合法List<Shape>参数add(Shape s)

这是一个人认为“泛型,我终于找到你了! ”的时候。然后他启动了一个 IDE,输入

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

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

T reference = <expression returning type S>

所以它想知道“ - 是Stype 的有效替代品T吗?” . 换句话说,类型层次结构中T的父级是S根据 Java 规则构造的吗?所以它仔细检查了它的旧 Java 书,发现没有,X<A>不是X<B>父级。非法代码。停止。

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

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

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

最后,为了将子类型关系移植AB泛型世界中,可以使用通配符?,但这对于一个全新的故事来说很重要。我只是预计在旧的 Java 书中写的是List<Rectangle>不扩展List<Shape>的,但它是List<? extends Shape>.

于 2012-09-21T16:33:35.710 回答
0

“这意味着这个列表将只接受 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

于 2012-09-21T11:59:02.783 回答
0

“这意味着这个列表将只接受类型 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。

于 2012-09-21T12:08:13.177 回答