为什么List<String>是List的子类型,但不是List<Object>的子类型?

4
可能有重复:
为什么List<Number>不是List<Object>的子类型? 在Java中,String不是Object的子类型吗?
那么,为什么我不能将类型为List<String>的对象传递给接受List<Object>作为参数的函数呢?但是,我可以将这样的对象传递给接受List作为参数的函数。

当列表在其类型参数上是协变的时,这是可能的。(协变意味着:(A <: B) => (List<A> <: List<B>),其中 <: 被解读为“是子类型”,而 => 被解读为“意味着”。)而且,只有在该列表是不可变的情况下才可能实现。 - missingfaktor
这个线程可能有助于理解这些概念。 - missingfaktor
5个回答

15

String在Java中不是Object的子类型吗?

是的,但这并不意味着List<String>List<Object>的子类型。

考虑以下示例:

    List<String> l1 = new ArrayList<String>();
    List<Object> l2 = l1;  // This is a compilation error in real Java
    l2.add(new Integer(42));
    String oops = l1.get(0);

如果(假设)List<String>List<Object> 的子类型,那么在最后一条语句中,您将会把一个Integer 分配给 String 变量。总之,这会破坏静态类型安全。


不过,我可以将这样的对象传递给接受List参数的函数。

是的,没错。但是您现在处理的是原始类型,并且在从列表中取出对象时需要使用显式类型转换。例如,上面的代码需要重写为以下形式:

    List<String> l1 = new ArrayList<String>();
    List l2 = l1;
    l2.add(new Integer(42));
    String oops = (String) l1.get(0);

请注意,类型转换意味着我们不得不用运行时类型安全性来代替静态类型安全性。(当然,在这种情况下,类型转换会在运行时失败,因为实例的类型错误。)


3

你混淆了参数化类型和具体类型。

在给定的 List<T> 中,T 是参数化类型。

List<String>List<Object> 没有任何关系,就像 List<String>List<Number> 没有任何关系一样。 类型List<String>,整个签名就是 类型。但是像所有事物一样,也存在例外;这些被称为 通配符参数化类型

List<?> 被称为 无限制通配符参数化类型,它的工作方式几乎与原始类型相同,它们在语义上相同,但会导致更多有关不安全转换的编译器警告。

List<? extends Object> 是一个有界通配符参数化类型,它允许你放置任何 extends Object 的东西,但由于 Java 中的每个类都 extends Object,所以它在语义上与前两个选项没有任何区别。

现在它们都会执行相同的功能,但它们将无法通过 instanceof 测试,它们不是相同的类型。

换句话说:

public void myfunction(final List<Object> list) {}

只接受 List<Object> 类型,ListList<?>List<Object> 的类型无关,尽管它们在语义上具有相同的功能。

type 可以是 ListList<Object>List<String>,但在考虑 List<T> 的类型时,不会考虑 <> 中的继承链,直到涉及 Wild Card Parameterized Types

以下是示例:

Collection<?> coll = new ArrayList<String>(); 
List<? extends Number> list = new ArrayList<Long>(); 

你不能实例化一个new Collection<?>,只能给它分配一个具体的实现。 List<Object>是整个类型。
在Java中,泛型与C++中的实现完全不同,除了名称有些令人困惑之外。

谢谢,那么 ListList<?> 可以互换使用吗? - dangerChihuahua007
1
@DavidFaux 绝对不是。 List 是一个原始类型,您可以从中添加/获取任何类型的 ObjectList <?> 是一个未知对象的集合,因此您可以从中获取 Object,但不能向其中添加任何内容。 - Jeffrey
@Jeffrey,你说的不正确,我在试图标记它时意外地点了赞同你的评论。原始类型和无界通配符参数化类型在语义上是相等的,因为它们的工作方式完全相同,只是通配符版本会发出更严格的编译器警告,提示未经检查的转换。 - user177800
1
@JarrodRoberson 只有在警告指的是错误时才是正确的。List<?> wl = whatever(); wl.add(new Object()) 将失败,但如果使用原始类型,则会编译并发出警告。因此,是的,ListList<?> 是可以互换的,但仅在与 List<Integer>List<Crayon> 相同的意义上...也就是说,不是。 - yshavit

3
在Java中,当S是T的子类型时,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集合,您想从另一个集合中添加所有项目。您不能为T类型的子类型S调用此方法,例如Collection<S>。理想情况下,这是可以接受的,因为您只向您的集合添加元素,而不会修改参数集合。
为了解决这个问题,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的协变,并阻止不安全的操作(例如将项目添加到集合中)。这样我们既获得了灵活性又保证了类型安全。

0

StringObject 的子类型,但这并不意味着 List 是 List 的子类型。这只是 Java 的工作方式。您可以在此处阅读更多信息。


0
除了上面提到的一切,如果要允许你想要的,你需要明确声明参数为 List<? extends T> (这意味着接受任何 T 子类,包括 T 本身),在这种情况下,List<? extends Object> 应该可以工作,但是 List<Object> 只限于以 Object 为模板的列表。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接