这是有效的Java?
这是有效的Java?
import java.util.Arrays; import java.util.List; class TestWillThatCompile { public static String f(List<String> list) { System.out.println("strings"); return null; } public static Integer f(List<Integer> list) { System.out.println("numbers"); return null; } public static void main(String[] args) { f(Arrays.asList("asdf")); f(Arrays.asList(123)); } }
- Eclipse 3.5说是的
- Eclipse 3.6说不
- Intellij 9说是的
- Sun的javac 1.6.0_20说是的
- GCJ 4.4.3说是的
- GWT编译器说是的
- 人群在我以前的Stackoverflow问题说不
我的Java理论理解说不!
了解JLS对此的看法是很有意思的。
这取决于你如何调用这些方法。 如果您希望从其他Java源代码中调用这些方法,那么由于Edwin答案中所述的原因,它被认为是无效的 。 这是Java语言的限制。
但是,并不是所有的类都需要从Java源代码生成(考虑所有使用JVM作为其运行时的语言:JRuby,Jython等)。 在字节码级别 ,JVM可以消除这两种方法的歧义,因为字节码指令指定了他们期待的返回types 。 例如,下面是用Jasmin编写的类,可以调用以下任一方法:
.class public CallAmbiguousMethod .super java/lang/Object .method public static main([Ljava/lang/String;)V .limit stack 3 .limit locals 1 ; Call the method that returns String aconst_null invokestatic TestWillThatCompile/f(Ljava/util/List;)Ljava/lang/String; ; Call the method that returns Integer aconst_null invokestatic TestWillThatCompile/f(Ljava/util/List;)Ljava/lang/Integer; return .end method
我使用以下命令将其编译为一个类文件:
java -jar jasmin.jar CallAmbiguousMethod.j
并使用它来调用它:
java CallAmbiguousMethod
看哪,输出是:
> java CallAmbiguousMethod string 数字
更新
Simon发布了一个调用这些方法的示例程序 :
import java.util.Arrays; import java.util.List; class RealyCompilesAndRunsFine { public static String f(List<String> list) { return list.get(0); } public static Integer f(List<Integer> list) { return list.get(0); } public static void main(String[] args) { final String string = f(Arrays.asList("asdf")); final Integer integer = f(Arrays.asList(123)); System.out.println(string); System.out.println(integer); } }
这里是生成的Java字节码:
> javap -c RealyCompilesAndRunsFine 从“RealyCompilesAndRunsFine.java”编译 类RealyCompilesAndRunsFine扩展java.lang.Object { RealyCompilesAndRunsFine(); 码: 0:aload_0 1:调用特定的#1; //方法java / lang / Object。“”:() 4:回报 public static java.lang.String f(java.util.List); 码: 0:aload_0 1:iconst_0 2:调用接口#2,2; // InterfaceMethod java / util / List.get:(I)Ljava / lang / Object; 7:checkcast#3; // class java / lang / String 10:结局 public static java.lang.Integer f(java.util.List); 码: 0:aload_0 1:iconst_0 2:调用接口#2,2; // InterfaceMethod java / util / List.get:(I)Ljava / lang / Object; 7:checkcast#4; // class java / lang / Integer 10:结局 public static void main(java.lang.String []); 码: 0:iconst_1 1:新arrays#3; // class java / lang / String 4:dup 5:iconst_0 6:ldc#5; //stringasdf 8:aastore 9:invokestatic#6; //方法java / util / Arrays.asList:([Ljava / lang / Object;)Ljava / util / List; 12:invokestatic#7; //方法f:(Ljava / util / List;)Ljava / lang / String; 15:astore_1 16:iconst_1 17:新阵容#4; // class java / lang / Integer 20:dup 21:iconst_0 22:双头123 24:invokestatic#8; //方法java / lang / Integer.valueOf:(I)Ljava / lang / Integer; 27:aastore 28:invokestatic#6; //方法java / util / Arrays.asList:([Ljava / lang / Object;)Ljava / util / List; 31:invokestatic#9; //方法f:(Ljava / util / List;)Ljava / lang / Integer; 34:astore_2 35:getstatic#10; // java / lang / System.out:Ljava / io / PrintStream; 38:aload_1 39:invokevirtual#11; //方法java / io / PrintStream.println:(Ljava / lang / String;)V 42:getstatic#10; // java / lang / System.out:Ljava / io / PrintStream; 45:aload_2 46:invokevirtual#12; //方法java / io / PrintStream.println:(Ljava / lang / Object;)V 49:回报
事实certificate,Sun编译器正在生成消除这些方法的必要字节码(参见上一个方法中的指令12和31)。
更新#2
Java语言规范build议,这实际上可能是有效的Java源代码。 在页449(§15.12方法调用expression式)我们看到这样的:
有可能没有最具体的方法,因为有两个或更多的方法是最具体的。 在这种情况下:
- 如果所有最具体的方法都具有覆盖等价(§8.4.2)签名,则:
- 如果其中一个最具体的方法没有被声明为抽象的,那么这是最具体的方法。
- 否则,如果所有最具体的方法都被声明为抽象的,并且所有最具体的方法的签名具有相同的擦除(§4.6), 则最具体的方法是在最具体方法的子集中任意select的最具体的返回types 。 但是,当且仅当在每个最大特定方法的throws子句中声明了该exception或其擦除时,最具体的方法才被视为抛出一个检查exception。
- 否则,我们说方法调用是不明确的,并发生编译时错误。
除非我错了,这种行为应该只适用于声明为抽象的方法,尽pipe…
更新#3
感谢ILMTitan的评论:
@Adam Paynter:你粗体的文本并不重要,因为只有两种方法是重写等价的情况下才是这种情况,Dan所示的情况并非如此。 因此,如果JLS在确定最具体的方法时考虑通用types,则决定因素必须是。 – ILMTitan
—编辑回应下面的评论—
好吧,这是有效的Java,但它不应该。 关键是它并不是真正依赖于返回types,而是依赖于已删除的generics参数。
这不适用于非静态方法,并且在非静态方法中明确禁止。 在类中尝试这样做会因为额外的问题而失败,首先是一个典型的类不像Class类那样是最终的。
这是一个不一致的,否则相当一致的语言。 即使在技术上允许的情况下,我们也会说,这应该是非法的。 这并不能真正增加语言的可读性,而且对于解决有意义的问题的能力几乎没有什么影响。 似乎要解决的唯一有意义的问题是,您是否熟悉这门语言,知道何时核心原则似乎被语言在解决types删除,generics以及由此产生的方法签名方面的内部矛盾所侵犯。
绝对的代码是可以避免的,因为用任何更有意义的方式解决同样的问题是微不足道的,唯一的好处是看看评论者/扩展者是否知道语言规范中一个尘土飞扬的脏angular落。
—原文如下—
虽然编译器可能允许,但答案仍然是否定的。
擦除将把List <String>和List <Integer>都变成一个未修改的列表。 这意味着你的两个“f”方法将具有相同的签名但不同的返回types。 返回types不能用来区分方法,因为这样做会返回到一个普通的超types时失败; 喜欢:
Object o = f(Arrays.asList("asdf"));
你有没有尝试捕获到variables的返回值? 也许编译器已经优化了一些东西,使得它没有踩到正确的错误代码。
有人质疑没有回答是:为什么它只会在Eclipse 3.6中触发编译错误?
原因如下: 这是一个function 。
在javac 7中,不pipe它们的返回types如何,两个方法都被认为是重复的(或名称冲突错误)。
现在,这种行为与javac 1.5更加一致,它报告方法上的名称冲突错误,并忽略它们的返回types。 在1.6版本中,当检测到重复方法时,做出了包括返回types的更改。
我们已经决定在3.6版本的所有合规级别(1.5,1.6,1.7)中进行这种更改,所以如果用javac 7编译代码,用户不会感到意外。
那么,如果我从规范的第8.4.2节的第一个列表中正确理解第三点,它就表示你的f()方法具有相同的签名:
http://java.sun.com/docs/books/jls/third_edition/html/classes.html#38649
这是真正回答这个问题的规范,而不是观察到的编译器X或IDE X的行为。通过查看这些工具,我们可以说的是该工具的作者是如何解释规范的。
如果我们应用第三项,我们得到:
... public static String f(List <String> list){ 的System.out.println( “串”); 返回null; } public static Integer f(List <String> list){ 的System.out.println( “数字”); 返回null; } ...
和签名匹配,所以有碰撞,代码不应该编译。
在规范上有效。
方法
m1
的签名是方法m2
的签名的子签名,如果其中之一
m2
与m1
具有相同的签名,或者
m1
的签名与m2
的签名的删除相同。
因此,这些不是彼此的子签名,因为List<String>
的删除不是List<Integer>
,反之亦然。
如果
m1
是m2
签名或m2
是m1
签名,那么两个方法签名m1
和m2
是否等同的。
所以这两个不是覆盖等效的(注意iff )。 而重载的规则是:
如果一个类的两个方法(无论是在同一个类中声明,还是由一个类inheritance,或者一个被声明,一个被inheritance)具有相同的名称,但是没有被覆盖等价的签名,那么方法名称被称为超载。
因此,这两个方法是超负荷的,一切都应该工作。
另外工作(这次用sun java 1.6.0_16)是
import java.util.Arrays; import java.util.List; class RealyCompilesAndRunsFine { public static String f(List<String> list) { return list.get(0); } public static Integer f(List<Integer> list) { return list.get(0); } public static void main(String[] args) { final String string = f(Arrays.asList("asdf")); final Integer integer = f(Arrays.asList(123)); System.out.println(string); System.out.println(integer); } }
从我可以告诉.class文件可以容纳两个方法,因为方法描述符保存参数,以及返回types。 如果返回types是相同的,那么描述符将是相同的,并且types擦除后方法将无法区分(因此它也不适用于void)。 http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html#7035
现在,用invoke_virtual调用方法需要方法描述符,所以实际上可以说你想要调用哪一个方法,所以看起来所有那些仍然有通用信息的编译器只是简单地把方法的描述符它与参数的genericstypes相匹配,所以它在字节码中被硬编码,哪个方法被调用(由它们的描述符区分,或者更确切地说由那些描述符中的返回types来区分),即使参数现在是一个List,没有generics信息。
虽然我觉得这种做法有点令人质疑,但是我必须说,我觉得你可以做到这一点很酷,并且认为仿制药的devise应该能够像这样工作(是的,我知道会的造成向后兼容性的问题)。
Javatypes推断(当你调用像Array.asList这样的静态generics方法时发生了什么)是复杂的,在JLS中没有很好的规定。 本文从2008年起对一些问题进行了非常有意思的描述,以及如何解决这个问题:
Javatypes推断被破坏:我们如何解决它?
Eclipse可以产生这样的字节码:
public class Bla { private static BigDecimal abc(List<BigDecimal> l) { return l.iterator().next().multiply(new BigDecimal(123)); } private static String abc(List<String> l) { return l.iterator().next().length() + ""; } public static void main(String[] args) { System.out.println(abc(Arrays.asList("asdf"))); System.out.println(abc(Arrays.<BigDecimal>asList(new BigDecimal(123)))); } }
输出:
4
15129
看起来编译器select了基于generics的最具体的方法。
import java.util.Arrays; import java.util.List; class TestWillThatCompile { public static Object f(List<?> list) { System.out.println("strings"); return null; } public static Integer f(List<Integer> list) { System.out.println("numbers"); return null; } public static void main(String[] args) { f(Arrays.asList("asdf")); f(Arrays.asList(123)); }
}
输出:
strings numbers