[英]Why does autoboxing not use valueOf() when invoking via reflection?
据我了解,以下代码应该打印"true"
,但是当我运行它时,它会打印"false"
。
public class Test {
public static boolean testTrue() {
return true;
}
public static void main(String[] args) throws Exception {
Object trueResult = Test.class.getMethod("testTrue").invoke(null);
System.out.println(trueResult == Boolean.TRUE);
}
}
根据JLS §5.1.7。 拳击转换:
如果被装箱的值
p
是true
、false
、一个byte
或一个\
到\
范围内的char
,或者一个介于-128
和127
(含)之间的int
或short
数,则让r 1
和r 2
为p
的任意两次装箱转换的结果。r 1 == r 2
总是如此。
但是,在通过反射调用的方法的情况下,装箱值始终通过new PrimitiveWrapper()
创建。
请帮助我理解这一点。
invoke将始终返回一个新的Object
。 任何返回的原语都被装箱。
...如果 [return] 值具有原始类型,则首先将其适当地包装在一个对象中。
您的问题是恰当地证明了该术语的歧义。 包装时即,它不使用通过Boolean.valueOf(布尔) 。
引用的部分已被多次重写,如Java 13 SE 规范不需要缓存盒装 Byte 对象中所述?
您已经引用了Java 7之前的版本使用:
如果被装箱的值p是
true
、false
、一个byte
、一个\
到\
范围内的char
,或者一个介于-128
和127
之间的int
或short
数,那么让r1
和r2
是任何两个装箱转换的结果的p
。r1 == r2
总是如此。
请注意,它忘记提及long
。
在Java 8 中,规范说:
如果被装箱的值
p
是介于-128
和127
之间的int
类型整数文字(第 3.10.1 节),或布尔文字true
或false
(第 3.10.3 节),或介于'\ '
和'\'
包含(第 3.10.4 节),然后让a
和b
是p
的任何两个装箱转换的结果。 总是a == b
的情况。
这仅适用于文字。
从Java 9 开始,规范说
如果被装箱的值
p
是对boolean
、char
、short
、int
或long
类型的常量表达式(第15.28 节)求boolean
的结果,并且结果为true
、false
,则为'\ '
和'\'
范围内的字符'\'
包含,或-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
来让反射立即使用字节码。
1.
具体的
如果通过反射调用方法
不包含在您引用的 JLS 的那部分中。 当您拥有作为另一种类型传递的类型的值时,您引用的那部分是关于类型转换的。 在这里,您正在考虑将布尔值转换为布尔值。
但是类型转换意味着做这样的事情:
Boolean b = true;
或者
boolean b = true;
Boolean b2 = b;
反射不是应用类型转换的机制。
当必要时,反射方法调用将布尔返回值包装到布尔对象中时,它不涉及您引用的 JLS 部分。
这就解释了为什么这里没有违反 JLS。
至于为什么反射不选择与这种行为保持一致:
那是因为在旧版本的 Java 中,反射存在于泛型之前。 泛型是自动装箱突然变得方便的原因,自动装箱是不复制包装原语的“公共”值似乎很聪明的原因。
所有这些都是在反射已经存在一段时间之后定义的,并且已经以特定的方式运行。 这意味着已经存在使用反射的现有 Java 代码,并且很可能是一些错误地依赖现有行为的现有代码。 更改现有行为会破坏现有代码,因此可以避免这种情况。
正如您在java.lang.reflect.Method
类中看到的, invoke
方法具有如下签名:
public Object invoke(Object obj, Object... args) { ... }
结果返回一个对象。
此外, Boolean.TRUE
定义为:
public static final Boolean TRUE = new Boolean(true);
这是一个true
价值的装箱对象。
通过在您的代码中评估trueResult == Boolean.TRUE
,您正在检查trueResult
和Boolean.TRUE
的引用是否相等。 因为==
评估值的相等性并且在引用的情况下,这意味着两个引用指向内存中的一个Object
吗?
很明显,这两个对象是不一样的(它们是两个独立的对象,在内存的不同部分实例化),所以trueResult == Boolean.TRUE
的结果是false
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.