Javagenerics代码使用javac编译,Eclipse Helios失败

我有以下测试类,它使用generics来重载方法。 它在使用javac编译时有效,无法在Eclipse Helios中编译。 我的java版本是1.6.0_21。

我读到的所有文章都表明Eclipse是正确的,这段代码不适用。 但是,使用javac和run编译时,会选择正确的方法。

这怎么可能?

谢谢!

import java.util.ArrayList; public class Test { public static void main (String [] args) { Test t = new Test(); ArrayList ss = new ArrayList(); ss.add("hello"); ss.add("world"); ArrayList is = new ArrayList(); is.add(1); is.add(2); System.out.println(t.getFirst(ss)); System.out.println(t.getFirst(is)); } public String getFirst (ArrayList ss) { return ss.get(0); } public Integer getFirst (ArrayList ss) { return ss.get(0); } } 

Java语言规范,第8.4.2节写道:

在类中声明具有覆盖等效签名(在下面定义)的两个方法是编译时错误。

如果m1是m2的子签名或m2是m1的子签名,则两个方法签名m1和m2是覆盖等价的。

方法m1的签名是方法m2的签名的子签名(如果有的话)

  • m2与m1具有相同的签名,或

  • m1的签名与m2签名的擦除相同。

显然,这些方法不是覆盖等价的,因为ArrayList不是ArrayListArrayList的擦除)。

因此宣布这些方法是合法的。 此外,方法调用表达式是有效的,因为通常只有一种方法匹配参数类型,因此通常是最具体的方法

编辑 :Yishai正确指出在这种情况下还有另一个限制。 Java语言规范,第8.4.8.3节写道:

如果类型声明T具有成员方法m1并且存在以T声明的方法m2或T的超类型以使得满足以下所有条件,则为编译时错误:

  • m1和m2具有相同的名称。
  • m2可从T访问。
  • m1的签名不是m2签名的子签名(第8.4.2节)。
  • m1或某些方法m1覆盖(直接或间接)具有与m2相同的擦除或某种方法m2覆盖(直接或间接)。

附录:关于实施,以及缺乏

与流行的概念相反,方法签名中的generics不会被删除。 generics以字节码 (Java虚拟机的指令集)擦除。 方法签名不是指令集的一部分; 它们被写入源代码中指定的类文件中。 (另外,也可以在运行时使用reflection查询此信息)。

想想看:如果类型参数完全从类文件中删除,那么您选择的IDE中的代码完成如何显示ArrayList.add(E)采用E类型的参数,而不是Object (= E的擦除)如果您没有附加JDK源代码? 当方法参数的静态类型不是E的子类型时,编译器如何知道抛出编译错误?

此代码是正确的,如JLS 15.12.2.5选择最具体的方法中所述

另外,考虑编码到接口:

 List ss = new ArrayList(); List is = new ArrayList(); // etc. 

正如@McDowell所说,修改后的方法签名出现在类文件中:

 $ javap build/classes/Test Compiled from "Test.java" public class Test extends java.lang.Object{ public Test(); public static void main(java.lang.String[]); public java.lang.String getFirst(java.util.ArrayList); public java.lang.Integer getFirst(java.util.ArrayList); } 

请注意,这与@meriton关于类文件的观察结果并不矛盾。 例如,此片段的输出

 Method[] methods = Test.class.getDeclaredMethods(); for (Method m : methods) { System.out.println(Arrays.toString(m.getGenericParameterTypes())); } 

显示main()的forms参数,以及两个generics类型参数:

 [class [Ljava.lang.String;] [java.util.ArrayList] [java.util.ArrayList] 

在Eclipse Helios中为我工作。 方法选择在编译时进行,编译器有足够的信息来执行此操作。

如果javac有错误就有可能。 Javac只是软件,并且像任何其他软件一样容易出错。

另外,Java语言规范非常复杂,并且在某些地方有点模糊,所以它也可能在Eclipse人员和javac人之间的解释上有所不同。

我首先在Eclipse支持渠道上询问这个问题。 他们通常非常善于接受这些事情并解释为什么他们认为他们是对的,或者承认他们错了。

为了记录,我认为Eclipse也就在这里。

你确定Eclipse也设置为使用Java 1.6吗?

经过一番研究,我得到了答案:

上面指定的代码不应该编译。 ArrayListArrayList ,在运行时仍然是ArrayList 。 但是你的代码不起作用,因为返回类型。 如果为两个方法设置相同的返回类型,javac将不会编译…

我读到Java 1.6中存在一个关于此错误的错误(已在Java 1.7中修复)。 所有关于返回类型……所以,您需要更改方法的签名。

Oracle的Bug数据库中存在错误6182950 。

Eclipse和javac使用不同的编译器。 Eclipse使用第三方编译器将您的代码转换为Java VM的字节码。 Javac使用Java编译器而不是Sun发布。 因此,相同的代码可能会产生略微不同的结果。 Netbeans我相信使用Sun的编译器,所以也在那里检查它。

要记住的一点是(所有?,当然有些例如NetBeans编辑器这样做)静态代码分析工具不会将返回类型或修饰符(私有/公共等)视为方法签名的一部分。

如果是这种情况,那么在类型擦除的帮助下, getFirst方法都会获得签名getFirst(java.util.ArrayList) ,从而触发名称冲突……