这是有效的 Java 吗?

2022-08-31 16:43:41

这是有效的 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对此有何看法会很有趣。


答案 1

这取决于您希望如何调用这些方法。如果您希望从其他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

我使用以下命令将其编译为类文件:

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

}

以下是生成的 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 子句中声明了该异常或其擦除时,才考虑引发已检查异常的最具体方法。
  • 否则,我们说方法调用是不明确的,并且发生了编译时错误。

除非我弄错了,否则此行为应该仅适用于声明为抽象的方法,但是......

更新 #3

感谢ILMTitan的评论:

@Adam Paynter:你的粗体文本并不重要,因为这只是两种方法覆盖等价的情况,Dan表明情况并非如此。因此,决定因素必须是JLS在确定最具体的方法时是否考虑了泛型类型。– ILMTitan


答案 2

--- 根据以下评论---编辑

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

这在非静态方法上不起作用,并且在非静态方法上是明确禁止的。由于额外的问题,尝试在类中执行此操作会失败,首先是典型的类不像类那样是最终的。

这是一种在其他方面相当一致的语言中的不一致。TI会四肢着地说它应该是非法的,即使技术上允许。它并没有真正增加语言的可读性,也没有给解决有意义的问题的能力增加任何东西。它似乎解决的唯一有意义的问题是,你是否对语言足够熟悉,知道它的核心原则似乎在解决类型擦除,泛型和最终的方法签名方面的内部不一致而受到违反。

绝对要避免代码,因为以任何更有意义的方式解决相同的问题是微不足道的,唯一的好处是查看审阅者/扩展者是否知道语言规范的肮脏角落。

--- 原始帖子关注---

虽然编译器可能允许这样做,但答案仍然是否定的。

擦除会将 List<String> 和 List<Integer>变成一个不加修饰的列表。这意味着两个“f”方法将具有相同的签名,但返回类型不同。返回类型不能用于区分方法,因为当您返回到通用超类型时,这样做将失败;喜欢:

Object o = f(Arrays.asList("asdf"));

您是否尝试过将返回值捕获到变量中?也许编译器已经优化了事情,以至于它没有踩到正确的错误代码。


推荐