【问题标题】:Java generics compiler error: incompatible typesJava泛型编译器错误:不兼容的类型
【发布时间】:2010-11-19 14:14:33
【问题描述】:

当我用 Java 做一些不是很花哨的事情时,我遇到了一个泛型错误,我无法理解为什么它不起作用。代码是:

package test;
import java.util.*;
public class TestClass {
  public static class A extends C{}
  public static class B extends C{}
  public static class C{}
  public static class D<T>{}
  public static class E<T>{}

  public static void main(String args[]){
      E<D<? extends C>> a = new E<D<A>>();
      E<D<? extends Object>> b = new E<D<? extends C>>();
      E<D<? extends A>> c = new E<D<A>>();
      E<D<? super A>> d = new E<D<A>>();
      D<? extends C> e = new D<A>();
      D<? extends A> f = new D<A>();
      D<? extends A> g = new D<A>();
  }
}

我在编译时遇到的错误是:

test/TestClass.java:11:不兼容的类型 找到:test.TestClass.E> 必需:test.TestClass.E> E> a = new E>(); ^ test/TestClass.java:12:不兼容的类型 发现:test.TestClass.E> 必需:test.TestClass.E> E> b = 新 E>(); ^ test/TestClass.java:13:不兼容的类型 找到:test.TestClass.E> 必需:test.TestClass.E> E> c = 新 E>(); ^ test/TestClass.java:14:不兼容的类型 找到:test.TestClass.E> 必需:test.TestClass.E> E> d = 新 E>(); ^ 4 个错误

如果找到E&lt;D&lt;? extends C&gt;&gt;,那肯定会匹配E&lt;D&lt;? extends Object&gt;&gt;,对吧?还是我错过了什么?

【问题讨论】:

  • 这是Java Puzzlers 下一版的好问题javapuzzlers.com
  • 我相信您偶然发现了一个边缘案例。非常有趣。
  • 如果你能理解它可能会有用:bit.ly/3RrNV3

标签: java generics


【解决方案1】:

也许这会帮助你理解:

    ArrayList<Object> aList = new ArrayList<String>();

这也不会因为类似的错误而编译。

编辑: 向咨询: http://www.ibm.com/developerworks/java/library/j-jtp01255.html

【讨论】:

  • 这不会编译,因为使用 aList 引用,您可以将对象插入到列表中,但它应该只包含字符串。
【解决方案2】:

这与之前发布的情况基本相同。基本上,在泛型情况下,您永远不能执行此分配:

想想这个例子:

ArrayList<Object> alist = new ArrayList<Number>();

这不会编译,因为它不是类型安全的。您可以添加字符串 aList。您正在尝试将保证为数字但可以是任何数字的对象列表分配给仅保证您包含对象但可以是任何对象的列表。如果编译器允许这种情况,它将放宽对允许哪些类型的对象进入列表的限制。这就是为什么必须使用通配符 ?,例如:

ArrayList<? extends Object> alist = new ArrayList<Number>();

对于编译器ArrayList&lt;? extends Object&gt;,意味着“某个特定类型的 ArrayList '?'我不知道,但我知道它扩展了 Object。这个 ArrayList 保证只包含这个未知“?”的元素。类型,因此只包含对象”。在这种情况下,编译器将不允许您执行 alist.add(2)。为什么会这样,因为编译器不知道列表元素的类型,也不能保证你可以插入 Integer 对象。

您认为D&lt;? extends Object&gt;D&lt;? extends C&gt; 的超类型是正确的。但是,List&lt;D&lt;? extends Object&gt;&gt; 不是List&lt;D&lt;? extends C&gt;&gt; 的子类型,您应该使用List&lt;? extends D&lt;? extends C&gt;&gt;

你的情况基本上相当于

ArrayList<D<? extends Object>> alist = new ArrayList<D<? extends C>>();

您遇到与上述相同的问题,右侧的列表只能包含类型参数为 C 的 D 类对象,并且您试图将其分配给列表(左侧)可以包含D类的对象,其类型参数可以是任何对象。

因此,如果编译器允许,您的代码将不是类型安全的,以下将失败。

ArrayList<D<? extends Object>> alist = new ArrayList<D<? extends C>>(); //< not type safe
alist.add(new D<Number>); //< oops

简而言之,您的具体示例需要以下内容:

// type parameter of left hand side is ? extends subtype
List<? extends D<? extends Object>> b = Arrays.asList(new D<A>(), new D<B>()); 

// type parameter of left hand side is identical
List<D<? extends C>> b = Arrays.asList(new D<A>(), new D<B>());

// type parameter of left hand side is ? extends subtype
List<? extends D<? extends C>> c = Arrays.asList(new D<A>());

// type parameter of left hand side is identical
List<D<A>> c = Arrays.asList(new D<A>());

希望这会有所帮助。

【讨论】:

    【解决方案3】:

    检查 Arrays.asList 调用返回的对象的类型。我会 它返回一个 List> 对象,它不能转换为 List>。

    【讨论】:

    • 看看我刚刚发布的新行。为什么那不是可铸造的?
    • 这一切都归结为我对 GrzegorzOledzki 的回答的评论。引用将允许您使用比原始泛型类接受的更广泛的类。
    【解决方案4】:

    &lt;? extends C&gt; 指定类型的上限,这意味着该类型必须从 C 扩展而来。&lt;? extends Object&gt; 显然更通用,因此不兼容。

    想想这个用例。通过指定上限,您期望实现某些最小接口。假设您在 C 中声明了一个 doIt() 方法。任何扩展 C 的类都将具有该方法,但不是每个扩展 Object 的类(Java 中的每个类)。

    【讨论】:

    • 你弄错了,我尝试分配 扩展到
    • 您可能需要考虑错误的方式:) 这与对象分配的类型安全完全相反。
    • 无论是否有意义,这两种方法都行不通。泛型类型必须完全匹配,而不仅仅是通过扩展类型。
    【解决方案5】:

    当分配给具有非通配符泛型类型T的变量(E&lt;T&gt;)时,被分配的对象必须恰好具有T作为其泛型类型(包括T的所有泛型类型参数,通配符和非通配符)。在您的情况下,TD&lt;A&gt;,它与 D&lt;? extends C&gt; 的类型不同。

    您可以使用通配符类型,因为D&lt;A&gt; 可分配给D&lt;? extends C&gt;

    E<? extends D<? extends C>> a = new E<D<A>();
    

    【讨论】:

    • 我认为这个答案最能说明问题。你可以做 E> e = 新 E>();但不是 E> e = new E>();
    【解决方案6】:

    比这更糟糕。你甚至不能这样做:

    List<D<?>> lDQ = Arrays.asList(new D<A>());
    

    如果您将程序更改如下,会更清楚:

    List<D<? extends C>> a = Arrays.asList(new D<A>(), new D<B>()); //compiles
    List<D<? extends Object>> b = a; //error
    

    基本上,您将 b 声明为可以接受一般内容的东西 (D&lt;anything&gt;),但您分配给它的列表仅接受更具体的内容(D&lt;A 和 B 的最近公共超类&gt;)。

    b 声明为List&lt;D&lt;? extends Object&gt;&gt; 意味着你可以说b.add(new D&lt;String&gt;()),但它实际上是List&lt;D&lt;? extends C&gt;&gt;,所以你不能。

    【讨论】:

      【解决方案7】:

      您不能继承泛型参数。假设您有以下参考资料:

      List<Object> a;
      List<String> b;
      

      现在您分配相同的列表:a = b;

      如果某些代码执行以下操作:

      a.add(new Integer(1));
      

      如果有人做了以下事情会发生什么:

      String s = b.get(0);
      

      你会得到一个字符串列表的整数。那应该行不通。这就是 List 和 List 不兼容的原因,尽管可以将 String 分配给 Object-reference。泛型不适用于继承。

      【讨论】:

        【解决方案8】:

        问题是List&lt;D&lt;? extends Object&gt;&gt; 基本上定义了一个新类,List&lt;D&lt;? extends C&gt;&gt; 也是如此。即使 c 扩展 Object 并不意味着 List&lt;D&lt;? extends C&gt;&gt; 扩展 List&lt;D&lt;? extends Object&gt;&gt; 并且分配工作需要这样做。

        尽管这个 series of articles 是为 .NET 平台编写的,但问题的描述仍然适用于 Java 泛型

        【讨论】:

        • 你需要用反引号转义那些尖括号。
        【解决方案9】:

        如果我们扩展您的示例,我认为这会更清楚。让我们将一些功能放入 E 并详细说明您的 main 方法的第一行:

        public static class E<T>{
            private final T thing;
        
            public void setThing(T thing) {
                this.thing = thing;
            }
        
            public T getThing() {
                return thing;
            }
        }
        
        public static void main(String[] args) {
            E<D<? extends C>> a1;
            E<D<A>> a2 = new E<D<A>>();
            a1 = a2; // this won't compile, but why?
        
            // these things are permissible on a1:
            a1.setThing(new D<A>());
            a2.setThing(new D<B>());
        
            // now let's try doing the same thing to a2:
            a2.setThing(new D<A>());
            a2.setThing(new D<B>()); // oops
        }
        

        新的main方法的最后一行是为什么a1不能设置为a2。如果编译器允许您这样做,那么您将能够将 D 放入一个声明为仅包含 D 的容器中。

        这在您的原始示例中并不明显的原因是您没有创建一个单独的变量来使用更具限制性的 E> 类型来引用该对象。但希望现在您可以看到,如果存在这样的引用,那么您尝试执行的分配会损害其类型安全性。

        【讨论】:

          【解决方案10】:

          不。

          如果是这样的话,它也会导致(未说明的)假设 - C 扩展 Object,并导致说 ...

          class C{ public void doSomethingMEaningful(); };

          列表 allAtSea = new ArrayList<...>(); 列表 allObjects = new ArrayList<...>(); allObjects.add(new Integer(88) ); ...

          allAtSea.addAll(allObjects); ...

          allAtSea.get(...).doSomethingMeaningful(...); // Uh-oh.. 这找到了整数 88


          C++ FAQ 在 21.3 提供了一个清晰的例子

          【讨论】:

          • 我认为 C++ 示例对 Java 问题没有帮助。
          • 我不同意,list-of-something 是否与 list-of-something-else 相同,这是关于理解继承和可替换性。
            那,IMO,是关于面向对象的基础;因此对 C++ 的引用应该是可以接受的
          • 这个例子仍然不正确(甚至是帮助,IMO)。 allAtSea.addAll() 行不编译,allObjects.add() 也不编译。
          猜你喜欢
          • 2011-04-18
          • 2023-03-29
          • 1970-01-01
          • 1970-01-01
          • 2015-03-27
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多