Java 通过通用包装器调用时没有选择最具体的方法?

Java not choosing the most specific method when called through a generic wrapper?

根据此 https://docs.oracle.com/javase/specs/jls/se18/html/jls-15.html#jls-15.12.2.5,Java 编译器将尝试选择最具体的方法来调用,当有多个适用且可访问的方法可用时。直觉是更具体的方法可以用不太具体的方法代替,反之亦然。

所以我有点惊讶,当我们将不明确的调用包装在通用包装器中时,这将不起作用,如下所示:

public class Test{
        static <T> void direct(T t) { System.out.println("generic");}
        static void direct(int t) { System.out.println("specific-int");}

        static <T> void indirect(T t) { direct(t);}
        
        public static void main ( String [] args ) {
            direct(1);    // print specific-int
            indirect(1);  // print generic
        }
     }

所以我们可以看到只要调用 direct 就可以按预期工作。但是当调用 indirect 时,会调用不太具体的方法。

如果我这样更改直接方法的类型,行为就会改变

    static void direct(short t) { System.out.println("specific-short");}

在这种情况下,两行都打印 generic(在任何一种情况下都不会调用 short 方法),这告诉我文字 1 被隐式转换为 int 首先。如果是这样,为什么不使用以 int 作为参数的更具体的方法调用它?

why wasn't it called with the more specific method that takes in int as an argument?

Java 编译器决定在编译时调用哪个方法。也就是说,对于 indirect 方法,它选择 direct 的重载,可以安全地调用 indirect.

的所有调用

唯一这样的重载是 direct(T) 方法:它接受任何 Object 参数,indirect(T) 也是如此。它无法调用 direct(int),因为并非所有 Object 都是 Integer

indirect 在使用 int 参数调用时没有任何不同。

2个不相关的原因,均导致通用版本:

拳击

类型参数必须是 Object 的子类型(目前,Valhalla 项目是管道中的 java 更新,可能会稍微改变这一点)。

因此,indirect 方法中的 direct(t) 调用不可能将 direct(int) 视为它的最具体版本,因为 indirect 中的 t 参数=] 不可能是 int.

具体来说,当您调用 indirect(1) 时,1 是一个 int,它不是 indirect 的有效值(因为 indirect 的参数是type T;T 是一个类型参数,下限为 java.lang.Object,并且 1 不是 Object 的有效值)。

然而,java 有'boxing'的概念,其中基元将自动转换为它们的盒装类型,但前提是代码不会' 否则编译。您可以使用 javap 看到这一点 - 您会注意到编译器将 1 替换为 Integer.valueOf(1) 以使其工作。

名字是 compile-time

选择哪种方法在编译时锁定。请注意覆盖(子类型实现完全相同的方法,即如果该方法用 @Override 注释,编译器将接受它) 完全是运行时的东西 和 java 总是从子类型中选择实现,但是 static 不“做”子类型,所以这里不相关。

你的2个direct方法不一样;在 JVM 级别,它们具有完全不同的名称,所以这 只是 关于 javac 选择了 2 个 direct 方法中的哪一个,因此,编译时间是唯一重要的时间在这里。

让我说清楚:两个direct方法没有相同的名称,因此在运行时JVM没有自由 根据类型选择一个或另一个。出于同样的原因,它无法将对 foo() 的调用替换为对 bar() 的调用 - 名称不同 .

indirect,方法不知道 T 是什么。 因此它不可能调用 direct(int),即使拳击不是这样! - 查找要选择的两个 direct 方法中的哪一个 在运行时完成。

重申

给定:

public class Parent {
  void foo(int i) { System.out.println("Parent-int"); }
  void foo(Integer i) { System.out.println("Parent-Integer"); }
}

class Child extends Parent {
    void foo(int i) { System.out.println("Child-int"); }
}

...

Parent p = new Child();
p.foo(5); // prints Child-int
p.foo(Integer.valueOf(5)); // prints Parent-Integer

最后的注释

您在评论中写道:

"which tells me that the literal 1 was implicitly casted to int in the first instance"

没有。 java 中的整数文字是 int - java 语言规范将它们定义为这样。你可以写 short x = 5; 只是因为一个特殊的豁免规则声明你不需要在那里进行强制转换,但是在语言方面,所有 non-decimal-pointed (和非 0x0p 形式)数字 AST 节点都被处理作为 int。如果例如,它们将被隐式转换。需要 long

你可以要求 javac 通过在末尾加上大写字母 L 来对待事物。鉴于:

void foo(byte i) {System.out.println("byte");
void foo(short i) {System.out.println("short");
void foo(int i) {System.out.println("int");
void foo(long i) {System.out.println("long");

foo(5); // prints 'int'
foo(5L); // prints 'long'

尽管“5”适合 'byte',但它是一个整数,因此选择了 int 变体。