为什么自动装箱在通过反射调用时不使用 valueOf()?
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. Boxing Conversion:
If the value p
being boxed is true
, false
, a byte
, or a char
in the range \u0000
to \u007f
, or an int
or short
number between -128
and 127
(inclusive), then let r<sub>1</sub>
and r<sub>2</sub>
be the results of any two boxing conversions of p
. It is always the case that r<sub>1</sub> == r<sub>2</sub>
.
然而,如果通过反射调用方法,则装箱值始终通过 new PrimitiveWrapper()
创建。
请帮助我理解这一点。
invoke 将 始终 return 一个新的 Object
。任何 returned 基元都被装箱。
...if the [return] value has a primitive type, it is first appropriately wrapped in an object.
您的问题是恰当地证明了术语的歧义。即在包装过程中,它 而不是 使用 Boolean.valueOf(boolean).
1.
具体
in case of method called via reflection
您引用的 JLS 部分未涵盖。当您有一个类型的值作为另一种类型传递时,您引用的那部分是关于 类型转换 的。这里你正在考虑将布尔值转换为布尔值。
但是类型转换意味着做类似的事情:
Boolean b = true;
或
boolean b = true;
Boolean b2 = b;
反射不是一种应用类型转换的机制。
当必要时,反射方法调用将布尔值 return 包装到布尔对象中,它不涉及您引用的 JLS 部分。
这解释了为什么这里没有违反 JLS。
2.
至于为什么反射没有选择与此行为保持一致:
那是因为在旧版本的 Java 中,反射先于泛型存在。泛型是自动装箱突然变得方便的原因,而自动装箱是不复制包装基元的 "common" 值看起来很聪明的原因。
所有这些都是在反射已经存在一段时间后定义的,并且已经以特定的方式运行。这意味着已经有 Java 代码在使用反射,并且很可能是一些现有代码错误地依赖了现有行为。更改现有行为会破坏现有代码,因此避免了这种情况。
正如您在 java.lang.reflect.Method
class 中所见,invoke
方法具有如下签名:
public Object invoke(Object obj, Object... args) { ... }
结果是 returns 个对象。
此外,Boolean.TRUE
定义为:
public static final Boolean TRUE = new Boolean(true);
这是一个价值 true
的盒装对象。
通过计算代码中的 trueResult == Boolean.TRUE
,您正在检查 trueResult
和 Boolean.TRUE
的引用是否相等。因为 ==
评估值的相等性并且在引用的情况下,这意味着两个引用是否指向内存中的一个 Object
?
很明显这两个对象不是同一个对象(它们是两个独立的对象,在内存的不同部分实例化),所以trueResult == Boolean.TRUE
的结果是false
.
引用的部分已多次重写,如
中所述
您引用的版本使用到 Java 7:
If the value p being boxed is true
, false
, a byte
, a char
in the range \u0000
to \u007f
, or an int
or short
number between -128
and 127
, then let r1
and r2
be the results of any two boxing conversions of p
. It is always the case that r1 == r2
.
注意忘记提到long
。
在Java 8中,规范说:
If the value p
being boxed is an integer literal of type int
between -128
and 127
inclusive (§3.10.1), or the boolean literal true
or false
(§3.10.3), or a character literal between '\u0000'
and '\u007f'
inclusive (§3.10.4), then let a
and b
be the results of any two boxing conversions of p
. It is always the case that a == b
.
仅适用于 文字。
因为 Java 9,规范说
If the value p
being boxed is the result of evaluating a constant expression (§15.28) of type boolean
, char
, short
, int
, or long
, and the result is true
, false
, a character in the range '\u0000'
and '\u007f'
inclusive, or an integer in the range -128
to 127
inclusive, then let a
and b
be the results of any two boxing conversions of p
. It is always the case that 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
让反射立即使用字节码。
根据我的理解,下面的代码应该打印 "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:
If the value
p
being boxed istrue
,false
, abyte
, or achar
in the range\u0000
to\u007f
, or anint
orshort
number between-128
and127
(inclusive), then letr<sub>1</sub>
andr<sub>2</sub>
be the results of any two boxing conversions ofp
. It is always the case thatr<sub>1</sub> == r<sub>2</sub>
.
然而,如果通过反射调用方法,则装箱值始终通过 new PrimitiveWrapper()
创建。
请帮助我理解这一点。
invoke 将 始终 return 一个新的 Object
。任何 returned 基元都被装箱。
...if the [return] value has a primitive type, it is first appropriately wrapped in an object.
您的问题是恰当地证明了术语的歧义。即在包装过程中,它 而不是 使用 Boolean.valueOf(boolean).
1.
具体
in case of method called via reflection
您引用的 JLS 部分未涵盖。当您有一个类型的值作为另一种类型传递时,您引用的那部分是关于 类型转换 的。这里你正在考虑将布尔值转换为布尔值。
但是类型转换意味着做类似的事情:
Boolean b = true;
或
boolean b = true;
Boolean b2 = b;
反射不是一种应用类型转换的机制。
当必要时,反射方法调用将布尔值 return 包装到布尔对象中,它不涉及您引用的 JLS 部分。
这解释了为什么这里没有违反 JLS。
-
2.
至于为什么反射没有选择与此行为保持一致:
那是因为在旧版本的 Java 中,反射先于泛型存在。泛型是自动装箱突然变得方便的原因,而自动装箱是不复制包装基元的 "common" 值看起来很聪明的原因。
所有这些都是在反射已经存在一段时间后定义的,并且已经以特定的方式运行。这意味着已经有 Java 代码在使用反射,并且很可能是一些现有代码错误地依赖了现有行为。更改现有行为会破坏现有代码,因此避免了这种情况。
正如您在 java.lang.reflect.Method
class 中所见,invoke
方法具有如下签名:
public Object invoke(Object obj, Object... args) { ... }
结果是 returns 个对象。
此外,Boolean.TRUE
定义为:
public static final Boolean TRUE = new Boolean(true);
这是一个价值 true
的盒装对象。
通过计算代码中的 trueResult == Boolean.TRUE
,您正在检查 trueResult
和 Boolean.TRUE
的引用是否相等。因为 ==
评估值的相等性并且在引用的情况下,这意味着两个引用是否指向内存中的一个 Object
?
很明显这两个对象不是同一个对象(它们是两个独立的对象,在内存的不同部分实例化),所以trueResult == Boolean.TRUE
的结果是false
.
引用的部分已多次重写,如
您引用的版本使用到 Java 7:
If the value p being boxed is
true
,false
, abyte
, achar
in the range\u0000
to\u007f
, or anint
orshort
number between-128
and127
, then letr1
andr2
be the results of any two boxing conversions ofp
. It is always the case thatr1 == r2
.
注意忘记提到long
。
在Java 8中,规范说:
If the value
p
being boxed is an integer literal of typeint
between-128
and127
inclusive (§3.10.1), or the boolean literaltrue
orfalse
(§3.10.3), or a character literal between'\u0000'
and'\u007f'
inclusive (§3.10.4), then leta
andb
be the results of any two boxing conversions ofp
. It is always the case thata == b
.
仅适用于 文字。
因为 Java 9,规范说
If the value
p
being boxed is the result of evaluating a constant expression (§15.28) of typeboolean
,char
,short
,int
, orlong
, and the result istrue
,false
, a character in the range'\u0000'
and'\u007f'
inclusive, or an integer in the range-128
to127
inclusive, then leta
andb
be the results of any two boxing conversions ofp
. It is always the case thata == 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
让反射立即使用字节码。