lac*_*547 59 java generics gcj javac
这是有效的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));
}
}
Run Code Online (Sandbox Code Playgroud)
我的Java理论的理解说没有!
知道JLS对它的评价会很有趣.
Ada*_*ter 29
这取决于您希望如何调用这些方法.如果您希望从其他Java源代码中调用这些方法,那么由于Edwin的答案中所示的原因,它被认为是无效的.这是Java语言的限制.
但是,并非所有类都需要从Java源代码生成(考虑使用JVM作为运行时的所有语言:JRuby,Jython等...).在字节码级别,JVM可以消除两种方法的歧义,因为字节码指令指定了它们期望的返回类型.例如,这是一个用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
Run Code Online (Sandbox Code Playgroud)
我使用以下命令将其编译为类文件:
java -jar jasmin.jar CallAmbiguousMethod.j
并使用以下方式调用:
java CallAmbiguousMethod
看,输出是:
> java CallAmbiguousMethod strings numbers
更新
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);
}
}
Run Code Online (Sandbox Code Playgroud)
这是生成的Java字节码:
>javap -c RealyCompilesAndRunsFine
Compiled from "RealyCompilesAndRunsFine.java"
class RealyCompilesAndRunsFine extends java.lang.Object{
RealyCompilesAndRunsFine();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."":()V
4: return
public static java.lang.String f(java.util.List);
Code:
0: aload_0
1: iconst_0
2: invokeinterface #2, 2; //InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
7: checkcast #3; //class java/lang/String
10: areturn
public static java.lang.Integer f(java.util.List);
Code:
0: aload_0
1: iconst_0
2: invokeinterface #2, 2; //InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
7: checkcast #4; //class java/lang/Integer
10: areturn
public static void main(java.lang.String[]);
Code:
0: iconst_1
1: anewarray #3; //class java/lang/String
4: dup
5: iconst_0
6: ldc #5; //String asdf
8: aastore
9: invokestatic #6; //Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
12: invokestatic #7; //Method f:(Ljava/util/List;)Ljava/lang/String;
15: astore_1
16: iconst_1
17: anewarray #4; //class java/lang/Integer
20: dup
21: iconst_0
22: bipush 123
24: invokestatic #8; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
27: aastore
28: invokestatic #6; //Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
31: invokestatic #9; //Method f:(Ljava/util/List;)Ljava/lang/Integer;
34: astore_2
35: getstatic #10; //Field java/lang/System.out:Ljava/io/PrintStream;
38: aload_1
39: invokevirtual #11; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
42: getstatic #10; //Field java/lang/System.out:Ljava/io/PrintStream;
45: aload_2
46: invokevirtual #12; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
49: return
事实证明,Sun编译器正在生成消除方法歧义所需的字节码(参见上一个方法中的指令12和31).
更新#2
在Java语言规范表明,这可能,事实上,是有效的Java源代码.在页449(§15.12方法调用表达式),我们看到:
可能没有方法是最具体的,因为有两种或更多种方法是最具体的.在这种情况下:
- 如果所有最大特定方法都具有覆盖等效(§8.4.2)签名,则:
- 如果其中一个最大特定方法未被声明为抽象,则它是最具体的方法.
- 否则,如果所有最大特定方法都被声明为抽象,并且所有最大特定方法的签名具有相同的擦除(第4.6节),则在具有最大特定方法的子集中任意选择最具体的方法.最具体的退货类型.但是,当且仅当在每个最大特定方法的throws子句中声明该异常或其擦除时,才会考虑使用最具体的方法抛出已检查的异常.
- 否则,我们说方法调用是不明确的,并且发生编译时错误.
除非我弄错了,否则这种行为应该只适用于声明为abstract的方法,不过......
更新#3
感谢ILMTitan的评论:
@Adam Paynter:你的粗体文本没关系,因为只有两种方法是覆盖等价的情况,Dan表明并非如此.因此,决定因素必须是在确定大多数特定方法时JLS将通用类型考虑在内. - ILMTitan
Edw*_*uck 13
---编辑以回应以下评论---
好的,所以它是有效的Java,但它不应该.关键是它不是真正依赖于返回类型,而是依赖于擦除的泛型参数.
这不适用于非静态方法,并且在非静态方法上明确禁止.尝试在课堂上这样做会因为额外的问题而失败,首先是典型的课程不像班级类那样是最终的.
在其他相当一致的语言中,这是不一致的.即使技术上允许,TI也会说它应该是非法的.它并没有真正为语言的可读性添加任何东西,并且它对解决有意义的问题的能力几乎没有增加.它似乎解决的唯一有意义的问题是你是否熟悉该语言,以了解它的核心原则何时似乎被语言在解析类型擦除,泛型和结果方法签名中的内部不一致所侵犯.
绝对是要避免的代码,因为以任何更有意义的方式解决同样的问题是微不足道的,唯一的好处是看看评论者/扩展者是否知道语言规范的尘土飞扬的肮脏角落.
---原帖如下---
虽然编译器可能允许它,但答案仍然是否定的.
Erasure将List <String>和List <Integer>都转换为一个朴素的List.这意味着您的两个"f"方法将具有相同的签名但返回类型不同.返回类型不能用于区分方法,因为当您返回到公共超类型时,这样做会失败; 喜欢:
Object o = f(Arrays.asList("asdf"));
Run Code Online (Sandbox Code Playgroud)
您是否尝试将返回的值捕获到变量中?也许编译器已经以这样的方式对事物进行了优化,即它没有踩到正确的错误代码.
它符合规范的有效性.
方法
m1的签名是方法签名的子签名(m2如果有的话)
m2具有相同的签名m1,或签名与签名
m1的擦除相同m2.
因此,这些不是彼此的子签名,因为擦除List<String>不是List<Integer>,反之亦然.
两个方法签名
m1并且m2是覆盖等效的,如果它们m1是子签名m2或是m2其子签名m1.
所以这两个不是覆盖等价的(注意iff).并且重载规则是:
如果一个类的两个方法(无论是在同一个类中声明,还是由一个类继承,或者一个是声明的,一个是继承的)都具有相同的名称,但签名不是覆盖等价的,那么方法名称就是超载.
因此,这两种方法都是重载的,一切都应该有效.
好吧,如果我在规范的第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("strings");
return null;
}
public static Integer f(List<String> list) {
System.out.println("numbers");
return null;
}
...
并且签名匹配,因此存在冲突并且代码不应该编译.