引用的部分已被多次重写,如 Java 13 SE 规范是否不需要缓存盒装字节对象?
您已经引用了使用 Java 7 的版本:
如果被装箱的值 p 是 、 、 a 、 a 或 介于 和 之间的或数字,则 let 和 是 的任意两个装箱转换的结果。总是这样。true
false
byte
char
\u0000
\u007f
int
short
-128
127
r1
r2
p
r1 == r2
请注意,它忘记提及。long
在Java 8中,规范说:
如果装箱的值是介于 和(§3.10.1) 之间的整数文本,或者是布尔文本或 (§3.10.3),或者介于 和 包含之间的字符文本 (§3.10.4),则让 和 成为 的任意两个装箱转换的结果。总是这样。p
int
-128
127
true
false
'\u0000'
'\u007f'
a
b
p
a == b
这仅适用于文本。
从Java 9开始,规范说
如果被装箱的值是计算类型 、 或 的常量表达式 (§15.28) 的结果,并且结果是 、 、、范围中的字符(包括)或范围中的整数(到 非独占),则 let 和 是 任意两个装箱转换的结果。总是这样。p
boolean
char
short
int
long
true
false
'\u0000'
'\u007f'
-128
127
a
b
p
a == b
这现在指的是常量表达式,包含和忘记(已在版本14中重新添加)。虽然这不是坚持文字值,但反射方法调用不是常量表达式,因此它不适用。long
byte
即使我们使用旧规范的措辞,也不清楚实现反射方法调用的代码是否进行装箱转换。原始代码源于不存在装箱转换的时代,因此它执行了包装器对象的显式实例化,只要代码包含显式实例化,就不会有装箱转换。
简而言之,反射操作返回的包装器实例的对象标识是未指定的。
从实现者的角度来看,处理第一个反射调用的代码是本机代码,它比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