被引用的部分已经被多次重写,正如在
Java 13 SE规范中是否需要缓存装箱的Byte对象?中讨论的那样。
你引用了使用到
Java 7的版本:
如果被装箱的值p是true
、false
、一个byte
、一个范围在\u0000
到\u007f
之间的char
,或者一个在-128
到127
之间的int
或short
数值,那么任意两个对p
进行装箱转换的结果r1
和r2
总是满足r1 == r2
。
请注意,它忘记提到
long
。
在Java 8中,规范说明如下:
如果被装箱的值p
是介于-128
和127
之间(包括-128和127)的整数字面量(§3.10.1),或布尔字面量true
或false
(§3.10.3),或者是介于'\u0000'
和'\u007f'
之间的字符字面量(§3.10.4),则让a
和b
分别表示p
的任意两个装箱转换的结果。始终满足a == b
。
上述仅适用于字面量。
自Java 9以来,规范说明如下:
如果被装箱的值p是通过计算一个常量表达式(参见§15.28)得到的,其类型为boolean、char、short、int或long,并且结果为true、false、范围在'\u0000'和'\u007f'之间的字符,或者范围在-128到127之间的整数,则让a和b分别为p的两个装箱转换的结果。总是成立a == b。
这现在指的是常量表达式,包括
long
,并忽略了
byte
(在14版本中已重新添加)。虽然这不坚持字面值,但反射方法调用不是常量表达式,因此不适用。
即使我们使用旧规范的措辞,也不清楚实现反射方法调用的代码是否承担了装箱转换。原始代码源自装箱转换不存在的时代,因此它执行了包装对象的显式实例化,只要代码包含显式实例化,就不会进行装箱转换。
简而言之,反射操作返回的包装实例的对象标识是未指定的。
从实施者的角度来看,处理第一个反射调用的代码是本地代码,比Java代码更难更改。但自JDK 1.3以来,当调用次数超过阈值时,这些本地方法访问器会被生成的字节码替换。由于重复调用是性能关键的部分,因此重要的是查看这些生成的访问器。自JDK 9以来,这些生成的访问器使用了装箱转换的等效方式。
因此,运行以下适应的测试代码:
import java.lang.reflect.Method;
public class Test
{
public static boolean testTrue() {
return true;
}
public static void main(String[] args) throws Exception {
int threshold = Boolean.getBoolean("sun.reflect.noInflation")? 0:
Integer.getInteger("sun.reflect.inflationThreshold", 15);
System.out.printf("should use bytecode after %d invocations%n", threshold);
Method m = Test.class.getMethod("testTrue");
for(int i = 0; i < threshold + 10; i++) {
Object trueResult = m.invoke(null);
System.out.printf("%-2d: %b%n", i, trueResult == Boolean.TRUE);
}
}
}
将在Java 9及更高版本中打印出来:
should use bytecode after 15 invocations
0 : false
1 : false
2 : false
3 : false
4 : false
5 : false
6 : false
7 : false
8 : false
9 : false
10: false
11: false
12: false
13: false
14: false
15: false
16: true
17: true
18: true
19: true
20: true
21: true
22: true
23: true
24: true
请注意,您可以调整JVM选项
-Dsun.reflect.inflationThreshold=number
以更改阈值,并使用
-Dsun.reflect.noInflation=true
让反射立即使用字节码。
更新:从JDK 18开始,值始终使用
valueOf(…)
进行装箱。
Boolean.TRUE
并不是“装箱转换的结果”。 - Jim Garrisonboolean
转换为Boolean
的情况下需要进行装箱转换。但是在反射的情况下,转换是从boolean
到Object
。因此,在Method.invoke()
的背后代码中,可以调用new Boolean(b)
将boolean
转换为Object
,而不违反 JLS 的规定。 - Thomas Kläger