这是有效的Java吗?

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)
  • Eclipse 3.5说是的
  • Eclipse 3.6说没有
  • Intellij 9说是的
  • Sun javac 1.6.0_20说是的
  • GCJ 4.4.3说是
  • GWT编译器说是的
  • 人群在我之前的Stackoverflow问题上说没有

我的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

更新

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);
    }

}
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

  • 仍然,生成的字节码不是回答"这是有效的java吗?"的问题.字节码只能回答"编译器的作者是否认为它是有效的java?" 最终,我们必须依靠规范来回答这个问题,然后查看编译器以确定它是否符合要求. (6认同)
  • +1用于字节码调查.我个人认为编译器使用List generics*参数类型*来确定在源代码中调用哪个方法,然后将其编译为基于*return type*区分的字节码,这是有点不正常的,但是你去了. (2认同)
  • @Tim:是的,它是字节码语义与语言语义不同的地方之一.我相信还有其他一些例子,但我无法忘记它们. (2认同)
  • 这究竟是什么问题的正确答案?关于生成字节代码的其他语言的观点与代码是否是有效Java无关.Java编译器验证Java源代码,这是实际问题.而不是其他基于JVM的语言是否可以区分由于生成字节代码的方法. (2认同)

Edw*_*uck 13

---编辑以回应以下评论---

好的,所以它是有效的Java,但它不应该.关键是它不是真正依赖于返回类型,而是依赖于擦除的泛型参数.

这不适用于非静态方法,并且在非静态方法上明确禁止.尝试在课堂上这样做会因为额外的问题而失败,首先是典型的课程不像班级类那样是最终的.

在其他相当一致的语言中,这是不一致的.即使技术上允许,TI也会说它应该是非法的.它并没有真正为语言的可读性添加任何东西,并且它对解决有意义的问题的能力几乎没有增加.它似乎解决的唯一有意义的问题是你是否熟悉该语言,以了解它的核心原则何时似乎被语言在解析类型擦除,泛型和结果方法签名中的内部不一致所侵犯.

绝对是要避免的代码,因为以任何更有意义的方式解决同样的问题是微不足道的,唯一的好处是看看评论者/扩展者是否知道语言规范的尘土飞扬的肮脏角落.

---原帖如下---

虽然编译器可能允许它,但答案仍然是否定的.

Erasure将List <String>和List <Integer>都转换为一个朴素的List.这意味着您的两个"f"方法将具有相同的签名但返回类型不同.返回类型不能用于区分方法,因为当您返回到公共超类型时,这样做会失败; 喜欢:

Object o = f(Arrays.asList("asdf"));
Run Code Online (Sandbox Code Playgroud)

您是否尝试将返回的值捕获到变量中?也许编译器已经以这样的方式对事物进行了优化,即它没有踩到正确的错误代码.


小智 11

一个质疑尚未得到回答的是:为什么它只会在Eclipse 3.6中触发编译错误?

原因如下:这是一个功能.

在javac 7中,两种方法都被认为是重复的(或名称冲突错误),无论它们的返回类型如何.

此行为现​​在与javac 1.5更加一致,它报告了方法上的名称冲突错误并忽略了它们的返回类型.仅在1.6中进行了更改,包括在检测重复方法时返回类型.

我们决定在3.6版本中对所有合规级别(1.5,1.6,1.7)进行此更改,以便用户在使用javac 7编译代码时不会对此更改感到惊讶.


Dav*_*ave 5

它符合规范的有效性.

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

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

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

因此,这些不是彼此的子签名,因为擦除List<String>不是List<Integer>,反之亦然.

两个方法签名m1并且m2是覆盖等效的,如果它们m1是子签名m2或是m2其子签名m1.

所以这两个不是覆盖等价的(注意iff).并且重载规则是:

如果一个类的两个方法(无论是在同一个类中声明,还是由一个类继承,或者一个是声明的,一个是继承的)都具有相同的名称,但签名不是覆盖等价的,那么方法名称就是超载.

因此,这两种方法都是重载的,一切都应该有效.


Don*_*son 5

好吧,如果我在规范的第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;
    }
...

并且签名匹配,因此存在冲突并且代码不应该编译.