Java泛型代码使用javac编译,Eclipse Helios失败

时间:2010-08-10 19:53:55

标签: java eclipse generics javac

我有以下测试类,它使用泛型来重载方法。它在使用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<String> ss = new ArrayList<String>();
        ss.add("hello");
        ss.add("world");
        ArrayList<Integer> is = new ArrayList<Integer>();
        is.add(1);
        is.add(2);
        System.out.println(t.getFirst(ss));
        System.out.println(t.getFirst(is));
    }   
    public String getFirst (ArrayList<String> ss) {
        return ss.get(0);
    }
    public Integer getFirst (ArrayList<Integer> ss) {
        return ss.get(0);
    }
}

8 个答案:

答案 0 :(得分:6)

Java Language Specification, section 8.4.2写道:

  

使用声明两个方法是编译时错误   覆盖等效签名   (在下面定义)在一个班级。

     

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

     

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

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

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

  •   

显然,这些方法不是覆盖等价的,因为ArrayList<String>不是ArrayListArrayList<Integer>的删除)。

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

编辑:Yishai正确地指出在这种情况下还有另一个限制。 Java Language Specification, section 8.4.8.3写道:

  

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

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

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

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

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

答案 1 :(得分:4)

此代码是正确的,如JLS 15.12.2.5 Choosing the Most Specific Method中所述。

另外,考虑编码到界面:

List<String> ss = new ArrayList<String>();
List<Integer> is = new ArrayList<Integer>();
// 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()的形式参数,以及两个泛型类型参数:

[class [Ljava.lang.String;]
[java.util.ArrayList<java.lang.String>]
[java.util.ArrayList<java.lang.Integer>]

答案 2 :(得分:3)

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

答案 3 :(得分:2)

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

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

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

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

答案 4 :(得分:2)

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

答案 5 :(得分:1)

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

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

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

bug 6182950 in Oracle's Bug Database

答案 6 :(得分:0)

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

答案 7 :(得分:0)

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

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