据我所知,以下代码应打印"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. Boxing Conversion:
如果要装箱的值
p
为true
,false
,byte
或char
到{{ {1}}或\u0000
和\u007f
(包括)之间的int
或short
数字,然后让-128
和127
为r1
的任何两次拳击转换的结果。r2
总是这样。
但是,如果通过反射调用方法,则始终通过p
创建带框值。
请帮助我理解这一点。
答案 0 :(得分:12)
invoke将始终返回新的Object
。所有返回的图元都装箱。
...如果[return]值具有原始类型,则首先将其适当包装在对象中。
您的问题是恰当地表明术语 的含糊不清。即在包装过程中,它不使用Boolean.valueOf(boolean)。
答案 1 :(得分:1)
引用的部分已被多次重写,如Is caching of boxed Byte objects not required by Java 13 SE spec?
中所述您引用的版本最多使用Java 7:
如果要装箱的值 p 为
true
,false
,byte
,char
,范围为\u0000
到\u007f
或int
和short
之间的-128
或127
数字,然后令r1
和r2
为结果p
的任何两次拳击转换中。r1 == r2
总是这样。
请注意,它忘记提及long
。
在Java 8中,规范说明:
如果要装箱的值
p
是int
和-128
之间(包括第3.10.1节)的类型127
的整数文字,或者布尔文字{{ 1}}或true
(§3.10.3),或介于false
和'\u0000'
之间的字符文字(§3.10.4),然后让'\u007f'
和{{ 1}}是a
的任何两次拳击转换的结果。b
总是这样。
仅适用于文字。
自Java 9以来,规范说
如果要装箱的值
p
是对类型为a == b
,p
,boolean
,{{ 1}}或char
,结果为short
,int
,介于long
和true
之间的字符,或范围从false
到'\u0000'
,然后让'\u007f'
和-128
是127
的两次装箱转换的结果。a
总是这样。
现在这是指常量表达式,包括b
并忘记了p
(已在版本14中重新添加)。尽管这并不要求字面值,但反射方法调用不是常量表达式,因此不适用。
即使我们使用旧规范的措词,也不清楚实现反射方法调用的代码是否带有装箱转换。原始代码源于不存在装箱转换的时间,因此它执行包装对象的显式实例化,并且只要代码包含显式实例,就不会进行装箱转换。
简而言之,反射操作返回的包装实例的对象身份未指定。
从实现者的角度来看,处理第一次反射调用的代码是本机代码,它比Java代码更难更改。但是从JDK 1.3开始,当调用次数超过阈值时,这些本机方法访问器将替换为生成的字节码。由于重复调用对性能至关重要,因此务必要查看这些生成的访问器。从JDK 9开始,这些生成的访问器使用等效的装箱转换。
因此,请运行以下经过修改的测试代码:
a == b
将在Java 9及更高版本下打印:
long
请注意,您可以使用JVM选项byte
来更改阈值,并使用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);
}
}
}
来使Reflection立即使用字节码。
答案 2 :(得分:0)
1。
具体
您引用的JLS部分未涵盖在通过反射调用方法的情况下
。您引用的那一部分是关于类型转换的,当您拥有一个作为其他类型传递的类型的值时。在这里,您正在考虑将布尔值转换为布尔值。
但是类型转换意味着要做类似的事情:
Boolean b = true;
或
boolean b = true;
Boolean b2 = b;
反射不是一种应用类型转换的机制。
当必要时,反射方法调用将布尔返回值包装到布尔对象中时,它不涉及您引用的JLS部分。
这说明了为什么这里没有违反JLS。
关于反射为什么仍未选择与此行为保持一致的原因:
这是因为在Java的旧版本中,反射在泛型之前就存在。泛型是自动装箱突然变得方便的原因,而自动装箱是不复制包装的基元的“ common”值似乎很明智的原因。
所有这些都是在反射已经存在一段时间并且已经以特定方式运行之后定义的。这意味着已经有使用反射的现有Java代码,并且很可能是某些不正确地依赖于现有行为的现有代码。更改现有行为会破坏现有代码,因此可以避免。
答案 3 :(得分:0)
在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
。