我有以下测试类,它使用泛型来重载方法。它在使用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);
}
}
答案 0 :(得分:6)
Java Language Specification, section 8.4.2写道:
使用声明两个方法是编译时错误 覆盖等效签名 (在下面定义)在一个班级。
如果m1是m2的子签名或m2是m1的子签名,则两个方法签名m1和m2是覆盖等价的。
方法m1的签名是方法m2的签名的子签名(如果是
)
m2与m1具有相同的签名,或
m1的签名与m2的签名擦除相同。
显然,这些方法不是覆盖等价的,因为ArrayList<String>
不是ArrayList
(ArrayList<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中修复)。所有关于返回类型......所以,您需要更改方法的签名。
答案 6 :(得分:0)
Eclipse和javac使用不同的编译器。 Eclipse使用第三方编译器将您的代码转换为Java VM的字节码。 Javac使用Java编译器而不是Sun发布。因此,相同的代码可能会产生略微不同的结果。 Netbeans我相信使用Sun的编译器,所以也在那里检查它。
答案 7 :(得分:0)
要记住的一点是(所有?,当然有些例如NetBeans编辑器这样做)静态代码分析工具不会将返回类型或修饰符(私有/公共等)视为方法签名的一部分
如果是这种情况,那么在类型擦除的帮助下,getFirst
方法都会获得签名getFirst(java.util.ArrayList)
,从而触发名称冲突......