为什么List <number>不是List <object>的子类型?</object> </number>

时间:2009-08-06 17:29:38

标签: java generics casting covariance

public void wahey(List<Object> list) {}

wahey(new LinkedList<Number>());

对方法的调用不会进行类型检查。我甚至无法按如下方式投射参数:

wahey((List<Object>) new LinkedList<Number>());

从我的研究中,我发现不允许这样做的原因是类型安全。如果我们被允许执行上述操作,那么我们可以拥有以下内容:

List<Double> ld;
wahey(ld);

在方法wahey中,我们可以在输入列表中添加一些字符串(因为参数保持List<Object>引用)。现在,在方法调用之后,ld引用类型为List<Double>的列表,但实际列表包含一些String对象!

这似乎与没有泛型的Java正常工作方式不同。例如:

Object o;
Double d;
String s;

o = s;
d = (Double) o;

我们在这里做的基本上是相同的,除了这将通过编译时检查,并且只在运行时失败。带有列表的版本将无法编译。

这让我相信这纯粹是关于泛型类型限制的设计决策。我希望对这个决定有所评论?

6 个答案:

答案 0 :(得分:9)

你在“没有泛型”的例子中所做的是一个演员表,这清楚表明你正在做一些类型不安全的事情。与泛型相当的是:

Object o;
List<Double> d;
String s;

o = s;
d.add((Double) o);

其行为方式相同(编译,但在运行时失败)。不允许您询问的行为的原因是因为它允许隐式类型不安全的操作,这在代码中更难注意到。例如:

public void Foo(List<Object> list, Object obj) {
  list.add(obj);
}

这看起来非常精细且类型安全,直到你这样称呼它:

List<Double> list_d;
String s;

Foo(list_d, s);

这也看起来类型安全,因为你作为调用者不一定知道Foo将如何处理它的参数。

因此,在这种情况下,您有两个看似类型安全的代码,这些代码最终都是类型不安全的。这很糟糕,因为它是隐藏的,因此难以避免并且难以调试。

答案 1 :(得分:8)

考虑一下......

List<Integer> nums = new ArrayList<Integer>();
List<Object> objs = nums
objs.add("Oh no!");
int x = nums.get(0); //throws ClassCastException

您可以将任何父类型的内容添加到列表中,这可能不是以前声明的内容,如上例所示,会导致各种问题。因此,这是不允许的。

答案 2 :(得分:4)

由于仿制药的工作方式,它们不是彼此的子类型。你想要的是声明你的功能:

public void wahey(List<?> list) {}

然后它将接受扩展Object的任何内容的List。你也可以这样做:

public void wahey(List<? extends Number> list) {}

这将允许您列出Number的子类的列表。

我建议你拿一份Maurice Naftalin&amp; amp;的“Java Generics and Collections”。菲利普瓦德勒。

答案 3 :(得分:3)

这里基本上有两个抽象维度,列表抽象和内容的抽象。沿列表抽象变化是完全正确的 - 例如,它是一个LinkedList或一个ArrayList - 但是进一步限制内容并不好说:这个(保存对象的列表)是一个(链表,只持有数字)。因为任何将其知道为(包含对象的列表)的引用都可以通过其类型的契约理解它可以保存任何对象。

这与您在非泛型示例代码中所做的完全不同,您已经说过:将此String视为Double。你试图说:把这个(仅包含数字的列表)视为(包含任何东西的列表)。它没有,并且编译器可以检测到它,所以它不会让你逃脱它。

答案 4 :(得分:1)

  

“我们在这里做的基本上是   同样的事情,除了这将通过   编译时检查,但只能失败   运行。带有列表的版本不会   编译“。

当您认为Java泛型的主要目的是在编译时而不是运行时使类型不兼容失败时,您所观察到的内容非常有意义。

来自java.sun.com

  

泛型为您提供了一种方式   传达集合的类型   到编译器,以便它可以   检查。一旦编译器知道了   元素类型的集合,   编译器可以检查你是否使用过   集合一致,可以   在值上插入正确的强制转换   被收集出来。

答案 5 :(得分:0)

在Java中,当List<S>List<T>的子类型时,S 不是 T的子类型。此规则提供类型安全性。

我们假设我们允许List<String>成为List<Object>的子类型。请考虑以下示例:

public void foo(List<Object> objects) {
    objects.add(new Integer(42));
}

List<String> strings = new ArrayList<String>();
strings.add("my string");
foo(strings); // this is not allow in java
// now strings has a string and an integer!
// what would happen if we do the following...??
String myString = strings.get(1);

因此,强制这样可以提供类型安全,但它也有缺点,它的灵活性较差。请考虑以下示例:

class MyCollection<T> {
    public void addAll(Collection<T> otherCollection) {
        ...
    }
}

这里有一个T的集合,您想要添加其他集合中的所有项目。对于Collection<S> S T子类型,您无法使用class MyCollection<T> { // Now we allow all types S that are a subtype of T public void addAll(Collection<? extends T> otherCollection) { ... otherCollection.add(new S()); // ERROR! not allowed (Here S is a subtype of T) } } 来调用此方法。理想情况下,这是可以的,因为您只是在集合中添加元素,而不是修改参数集合。

为了解决这个问题,Java提供了他们所称的&#34;通配符&#34;。通配符是一种提供协方差/逆变的方法。现在考虑以下使用通配符:

{{1}}

现在,通过通配符,我们允许类型T中的协方差,并且我们阻止非类型安全的操作(例如将项添加到集合中)。这样我们就可以获得灵活性和类型安全性。