繁体   English   中英

为什么List <Number>不是List <Object>的子类型?

[英]Why is List<Number> not a sub-type of List<Object>?

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>类型的List<Double> ,但实际列表包含一些String对象!

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

Object o;
Double d;
String s;

o = s;
d = (Double) o;

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

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

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

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将如何处理它的参数。

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

考虑一下......

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

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

由于泛型如何工作,它们不是彼此的子类型。 你想要的是声明你的功能:

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

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

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

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

我建议你拿一份Maurice Naftalin和Philip Wadler的“Java Generics and Collections”。

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

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

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

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

来自java.sun.com

泛型提供了一种将集合类型传递给编译器的方法,以便可以检查它。 一旦编译器知道集合的元素类型,编译器就可以检查您是否一直使用了集合,并且可以在从集合中取出的值上插入正确的强制转换。

在Java中,当ST的子类型时, List<S> 不是 List<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 理想情况下,这是可以的,因为您只是在集合中添加元素,而不是修改参数集合。

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

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)
     }
} 

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM