繁体   English   中英

为什么在通过反射调用时自动装箱不使用 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。 拳击转换

如果被装箱的值ptruefalse 、一个byte或一个\\范围内的char ,或者一个介于-128127 (含)之间的intshort数,则让r 1r 2p的任意两次装箱转换的结果。 r 1 == r 2总是如此。

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

请帮助我理解这一点。

invoke始终返回一个新的Object 任何返回的原语都被装箱。

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

您的问题是恰当地证明了该术语的歧义。 包装时即,它使用通过Boolean.valueOf(布尔)

引用的部分已被多次重写,如Java 13 SE 规范不需要缓存盒装 Byte 对象中所述?

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

如果被装箱的值ptruefalse 、一个byte 、一个\\范围内的char ,或者一个介于-128127之间的intshort数,那么让r1r2是任何两个装箱转换的结果的p r1 == r2总是如此。

请注意,它忘记提及long

Java 8 中,规范说:

如果被装箱的值p是介于-128127之间的int类型整数文字(第 3.10.1 节),或布尔文字truefalse (第 3.10.3 节),或介于'\''\'包含(第 3.10.4 节),然后让abp的任何两个装箱转换的结果。 总是a == b的情况。

这仅适用于文字

Java 9 开始,规范说

如果被装箱的值p是对booleancharshortintlong类型的常量表达式(第15.28 节)求boolean的结果,并且结果为truefalse ,则为'\''\'范围内的字符'\'包含,或-128127范围内的整数,然后让abp的任何两个装箱转换的结果。 总是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。

    2.

至于为什么反射不选择与这种行为保持一致:

那是因为在旧版本的 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 ,您正在检查trueResultBoolean.TRUE的引用是否相等。 因为==评估值的相等性并且在引用的情况下,这意味着两个引用指向内存中的一个Object吗?

很明显,这两个对象是不一样的(它们是两个独立的对象,在内存的不同部分实例化),所以trueResult == Boolean.TRUE的结果是false

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM