为什么自动装箱在通过反射调用时不使用 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,您正在检查 trueResultBoolean.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 让反射立即使用字节码。