【问题标题】:Why is List<String> a subtype of List, but not of List<Object>? [duplicate]为什么 List<String> 是 List 的子类型,而不是 List<Object> 的子类型? [复制]
【发布时间】:2012-06-27 07:49:02
【问题描述】:

可能重复:
Why is List<Number> not a sub-type of List<Object>?

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

那么,为什么我不能将List&lt;String&gt; 类型的对象传递给接受List&lt;Object&gt; 作为参数的函数呢?不过,我可以将这样的对象传递给接受List 作为参数的函数。

【问题讨论】:

  • 当列表的类型参数协变时,这是可能的。 (协方差的意思是:(A &lt;: B) =&gt; (List&lt;A&gt; &lt;: List&lt;B&gt;),其中&lt;: 被读作is subtype of=&gt; 被读作implies。)只有当上述列表是不可变的时才有可能。
  • This thread 可能有助于理解这些概念。

标签: java generics


【解决方案1】:

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

是的,但这不会使List&lt;String&gt; 成为List&lt;Object&gt; 的子类型。

考虑这个例子:

    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&lt;String&gt;List&lt;Object&gt; 的子类型,那么您最终会在最后一个语句中将Integer 分配给String 变量。简而言之,我们会破坏静态类型安全性。


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

是的,这是真的。但是随后您正在处理原始类型,并且在将对象拉出列表时必须使用显式类型转换。例如,上面需要重写如下:

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

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

【讨论】:

    【解决方案2】:

    您将参数化类型与具体类型混淆了。

    给定List&lt;T&gt;T 是参数化类型。

    List&lt;String&gt;List&lt;Object&gt; 没有任何类型关联,就像 List&lt;String&gt;List&lt;Number&gt; 关联一样。 typeList&lt;String&gt;整个 签名是 type。但就像所有事情一样,也有例外。这些被称为Wildcard Parameterized Types

    List&lt;?&gt; 就是所谓的Unbounded Wildcard Parameterized Type,它的工作方式与原始类型非常相似,它们在语义上是相同的,但会导致更多关于不安全转换的编译器警告。

    List&lt;? extends Object&gt; 是一个有界通配符参数化类型,并且是一种允许您放置任何 extends Object 的类型,但由于 Java 中的每个类 extends Object 它在语义上与前两个选项没有任何不同.

    现在它们的功能都相同,但它们会通过instanceof 测试,它们是相同的类型。

    换句话说:

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

    只接受List&lt;Object&gt;typeListtypeList&lt;Object&gt;List&lt;?&gt; 无关,即使它们在语义上都具有相同的功能。

    typeListList&lt;Object&gt;List&lt;String&gt;&lt;&gt; 中的继承链在涉及List&lt;T&gt; 的类型时不考虑,直到你进入Wild Card Parameterized Types

    以下是示例:

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

    你不能实例化一个 new Collection&lt;?&gt; 只分配一个具体的实现给它。

    List&lt;Object&gt; 是整个类型。

    Java 中的泛型与 C++ 中的实现完全不同,只是名称令人困惑。

    【讨论】:

    • 谢谢,ListList&lt;?&gt; 可以互换使用吗?
    • @DavidFaux 绝对不是。 List 是原始类型,您可以从中添加/获取任何类型的 ObjectList&lt;?&gt; 是未知对象的集合,因此您可以从中获取Objects,但不能添加任何对象。
    • @Jeffrey 你所说的不正确,我在试图标记它时不小心对你的评论投了赞成票。 Raw Types and Unbounded Wildcard Parameterized Types 在语义上是相等的,因为它们的工作方式完全相同,通配符版本只是针对未经检查的强制转换发出更严格的编译器警告。
    • @JarrodRoberson 只有当警告意味着错误时,这才是正确的。 List&lt;?&gt; wl = whatever(); wl.add(new Object()) 将失败,但如果您使用原始类型,它将在编译时出现警告。所以是的,ListList&lt;?&gt; 是可以互换的,但只是在相同的意义上 List&lt;Integer&gt;List&lt;Crayon&gt; 是......也就是说,不是。
    【解决方案3】:

    在 Java 中,List&lt;S&gt; 不是List&lt;T&gt; 的子类型,而ST 的子类型。此规则提供类型安全。

    假设我们允许List&lt;String&gt; 成为List&lt;Object&gt; 的子类型。考虑以下示例:

    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 的集合,您想添加另一个集合中的所有项目。对于TS 子类型,您不能使用Collection&lt;S&gt; 调用此方法。理想情况下,这是可以的,因为您只是将元素添加到集合中,而不是修改参数集合。

    为了解决这个问题,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 中的协变,并阻止非类型安全的操作(例如,将项目添加到集合中)。这样我们就获得了灵活性和类型安全性。

    【讨论】:

      【解决方案4】:

      除了上面所说的一切,为了允许你想要的,你需要将参数显式声明为List&lt;? extends T&gt;(这意味着,接受任何 T 的子类,包括 T),在这种情况下,List&lt;? extends Object&gt; 应该可以工作,但是List&lt;Object&gt; 仅限于对象模板列表

      【讨论】:

        【解决方案5】:

        StringObject 的子类型,是的,但这并不意味着 List 是 List 的子类型。这就是 Java 的工作方式。你可以阅读更多here

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-07-17
          • 2021-09-11
          • 2021-07-06
          • 2021-02-17
          • 1970-01-01
          相关资源
          最近更新 更多