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
变体。
根据此 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
变体。