为什么自动装箱在通过反射调用时不使用 valueOf()?

2022-09-02 10:46:00

根据我的理解,以下代码应该打印,但是当我运行它时,它会打印。"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。拳击转换

如果被装箱的值是 、 、 a 或 介于 的范围内 , 或 介于 和 之间的或数字 (含),则 let 和 是 的任意两个装箱转换的结果。总是这样。ptruefalsebytechar\u0000\u007fintshort-128127r1r2pr1 == r2

但是,在通过反射调用方法的情况下,始终通过 创建框值。new PrimitiveWrapper()

请帮助我理解这一点。


答案 1

invoke始终返回一个新的 .任何返回的基元都装箱。Object

...如果 [return] 值具有基元类型,则首先将其适当地包装在对象中。

您的问题正在适当地证明该术语的模糊性。即在包装过程中,它不使用Boolean.valueOf(boolean)


答案 2

引用的部分已被多次重写,如 Java 13 SE 规范是否不需要缓存盒装字节对象?

您已经引用了使用 Java 7 的版本

如果被装箱的值 p 是 、 、 a 、 a 或 介于 和 之间的或数字,则 let 和 是 的任意两个装箱转换的结果。总是这样。truefalsebytechar\u0000\u007fintshort-128127r1r2pr1 == r2

请注意,它忘记提及。long

Java 8中,规范说:

如果装箱的值是介于 和(§3.10.1) 之间的整数文本,或者是布尔文本或 (§3.10.3),或者介于 和 包含之间的字符文本 (§3.10.4),则让 和 成为 的任意两个装箱转换的结果。总是这样。pint-128127truefalse'\u0000''\u007f'abpa == b

这仅适用于文本

Java 9开始,规范说

如果被装箱的值是计算类型 、 或 的常量表达式 (§15.28) 的结果,并且结果是 、 、、范围中的字符(包括)或范围中的整数(到 非独占),则 let 和 是 任意两个装箱转换的结果。总是这样。pbooleancharshortintlongtruefalse'\u0000''\u007f'-128127abpa == b

这现在指的是常量表达式,包含和忘记(已在版本14中重新添加)。虽然这不是坚持文字值,但反射方法调用不是常量表达式,因此它不适用。longbyte

即使我们使用旧规范的措辞,也不清楚实现反射方法调用的代码是否进行装箱转换。原始代码源于不存在装箱转换的时代,因此它执行了包装器对象的显式实例化,只要代码包含显式实例化,就不会有装箱转换。


简而言之,反射操作返回的包装器实例的对象标识是未指定的。


从实现者的角度来看,处理第一个反射调用的代码是本机代码,它比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


推荐