【问题标题】:Quick java generics question快速java泛型问题
【发布时间】:2023-03-22 21:01:01
【问题描述】:

我认为我并不真正了解 Java 泛型。这两种方法有什么区别?以及为什么第二个不能编译,错误如下图。

谢谢

static List<Integer> add2 (List<Integer> lst) throws Exception {
    List<Integer> res = lst.getClass().newInstance();
    for (Integer i : lst) res.add(i + 2);
    return res;
}

.

static <T extends List<Integer>> T add2 (T lst) throws Exception {
    T res = lst.getClass().newInstance();
    for (Integer i : lst) res.add(i + 2);
    return res;
}

Exception in thread "main" java.lang.RuntimeException: Uncompilable source code - incompatible types
  required: T
  found:    capture#1 of ? extends java.util.List

【问题讨论】:

  • 报错信息没有问题;谁说应用程序不能即时编译 java 文件?实际上很多应用程序都可以!错误很明显:方法无法编译;问题很清楚,为什么它不能编译。 @Bruno 没有回答。

标签: java generics


【解决方案1】:

要编译第二种方法,你必须将newInstace()的结果转换为T

static <T extends List<Integer>> T add2 (T lst) throws Exception {
  T res = (T) lst.getClass().newInstance();
  for (Integer i : lst) res.add(i + 2);
  return res;
}

关于这两种方法的区别,我们先不谈实现,只考虑签名。

代码编译后,两个方法的签名完全相同(因此如果名称相同,编译器会报错)。这是因为所谓的类型擦除

在 Java 中,所有类型参数在编译后都会消失。它们被最通用的原始类型所取代。在这种情况下,这两个方法都将编译为List add2(List)

现在,这将显示两种方法之间的区别:

class Main {
  static <T extends List<Integer>> T add1(T lst) { ... }
  static List<Integer> add2(List<Integer> lst) { ... }
  public static void main(String[] args) {
    ArrayList<Integer> l = new ArrayList<Integer>();
    ArrayList<Integer> l1 = add1(l);
    ArrayList<Integer> l2 = add2(l); // ERROR!
  }
}

标记为// ERROR! 的行将无法编译。

在第一个方法add1中,编译器知道它可以将结果赋给ArrayList&lt;Integer&gt;类型的变量,因为签名声明方法的返回类型与参数的返回类型完全相同.由于参数的类型为ArrayList&lt;Integer&gt;,编译器会将T 推断为ArrayList&lt;Integer&gt;,这将允许您将结果分配给ArrayList&lt;Integer&gt;

在第二种方法中,编译器只知道它将返回一个List&lt;Integer&gt; 的实例。无法确定它是否是ArrayList&lt;Integer&gt;,因此您必须进行显式转换,ArrayList&lt;Integer&gt; l2 = (ArrayList&lt;Integer&gt;) add2(l);。请注意,这不会解决问题:您只是告诉编译器停止抱怨并编译代码。您仍然会收到警告(unchecked cast),可以通过使用@SuppressWarnings("unchecked") 注释方法来消除警告。现在编译器会安静下来,但您可能仍会在运行时收到ClassCastException

【讨论】:

    【解决方案2】:

    第一个被指定接受一个List并返回一个List。 List 是一个接口,这意味着实现 List 的某个具体类的实例作为参数传递,结果返回了实现 List 的某个其他具体类的实例,这两个类之间没有任何进一步的关系而不是他们都实现了 List。

    第二个加强了这一点:它被指定接受某个实现 List 的类作为参数,并返回完全相同的类或后代类的实例作为结果。

    例如,您可以像这样调用第二个:

    ArrayList list; // initialization etc not shown
    ArrayList result = x.add2(list);
    

    但不是第一个,除非您添加了类型转换。

    那有什么用是另一个问题。 ;-)

    @Bruno Reis 解释了编译错误。

    【讨论】:

      【解决方案3】:

      为什么第二个不能编译,错误如下图。

      显示的错误是实际上报告您尝试运行代码编译失败。最好将 IDE 配置为不运行出现编译错误的代码。或者如果你坚持让这种情况发生,至少报告实际的编译错误以及行号等。

      【讨论】:

      • @Luigi Plinge 如果它说存在编译器错误,您可以相信它。警告是另一锅鱼……这就是为什么它们是警告。
      • @EJP 我多次不相信 IDE。我的 IntelliJ 在 OpenJDK 或 Ubuntu 上的 Sun JDK(所有最新版本)上运行,有时它会在一个巨大的项目中变得很高并且完全疯狂,并开始在我的代码中显示完全无意义的错误。如果我剪切并粘贴显示错误的行,IDE 将看到代码是正确的...
      • 另外,我开始不再相信编译器了。 Sun 的编译器在泛型方面非常有问题!它抱怨由 Eclipse 的编译器编译的完全有效的代码,甚至没有警告!
      • @Bruno Reis - 这可能真的是 Eclipse 编译器中的一个错误......接受无效代码。确保您拥有两个编译器的最新版本。
      • @Stephen 实际上这是 Sun 编译器中的一个错误,很久以前就报告并接受了,但仍然没有像 Java 中的往常一样修复。 bugs.sun.com/view_bug.do?bug_id=6932571
      【解决方案4】:

      “我认为我并不真正了解 Java 泛型。”

      没有人...

      这个问题与有趣的返回类型 getClass() 有关。请参阅它的 javadoc。还有this recent thread

      在您的两个示例中,lst.getClass() 返回 Class&lt;? extends List&gt;,因此,newInstance() 返回 ? extends List - 或者更正式地说,是 javac 引入的新类型参数 W 其中W extends List

      在您的第一个示例中,我们需要将W 分配给List&lt;Integer&gt;。这是assignment conversion 允许的。首先,W 可以转换为List,因为ListW 的超类型。然后由于List 是原始类型,因此允许进行可选的未经检查的转换,它将List 转换为List&lt;Integer&gt;,并带有强制编译器警告。

      在第二个例子中,我们需要将W 分配给T。我们在这里运气不好,没有从W 转换为T 的路径。这是有道理的,因为据 javac 目前所知,WT 可能是 List 的两个不相关的子类。

      当然,我们知道WT,如果允许,分配本来是安全的。这里的根本问题是getClass() 丢失了类型信息。如果 x.getClass() 返回 Class&lt;? extends X&gt; 而没有擦除,那么您的两个示例都将在没有警告的情况下编译。它们确实是类型安全的。

      【讨论】:

        【解决方案5】:

        泛型是保证类型安全的一种方式。 例如:

        int[] arr = new int[4];
           arr[0] = 4; //ok
           arr[1] = 5; //ok
           arr[2] = 9; //ok
           arr[3] = "Hello world"; // you will get an exception saying incompatible 
        

        类型。

        Java 中的默认数组是类型安全的。整数数组仅用于 包含整数,仅此而已。

        现在:

        ArrayList arr2 =new ArrayList();
           arr2.add(4); //ok
           arr2.add(5); //ok
           arr2.(9); //ok
        
        
           int a = arr2.get(0);
           int b = arr2.get(1);
           int c = arr3.get(2);
        

        您将获得一个异常,例如无法强制转换 Object 实例转整数。

        原因是 ArrayList 存储对象而不是像 上面的数组。

        正确的方法是显式转换为整数。你必须这样做 因为类型安全尚未得到保证。 例如:

        int a = (int)arr2.get(0);

        要为集合使用类型安全,您只需指定集合包含的对象的类型。 例如:

         ArrayList<Integer> a = new ArrayList<Integer>();
        
        
        After insertion into the data structure, you can simply retrieve it like you 
        would do with an array.
        

        例如:

        int a = arr2.get(0);

        【讨论】:

          猜你喜欢
          • 2011-11-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多