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

时间:2019-01-08 08:17:17

标签: java autoboxing

据我所知,以下代码应打印"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

  

如果要装箱的值ptruefalsebytechar到{{ {1}}或\u0000\u007f(包括)之间的intshort数字,然后让-128127r1的任何两次拳击转换的结果。 r2总是这样。

但是,如果通过反射调用方法,则始终通过p创建带框值。

请帮助我理解这一点。

4 个答案:

答案 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 truefalsebytechar,范围为\u0000\u007fintshort之间的-128127数字,然后令r1r2为结果p的任何两次拳击转换中。 r1 == r2总是这样。

请注意,它忘记提及long

Java 8中,规范说明:

如果要装箱的值pint-128之间(包括第3.10.1节)的类型127的整数文字,或者布尔文字{{ 1}}或true(§3.10.3),或介于false'\u0000'之间的字符文字(§3.10.4),然后让'\u007f'和{{ 1}}是a的任何两次拳击转换的结果。 b总是这样。

仅适用于文字

Java 9以来,规范说

如果要装箱的值p是对类型为a == bpboolean,{{ 1}}或char,结果为shortint,介于longtrue之间的字符,或范围从false'\u0000',然后让'\u007f'-128127的两次装箱转换的结果。 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。

    2。

关于反射为什么仍未选择与此行为保持一致的原因:

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

很明显,这两个对象是不相同的(它们是两个独立的对象,并在内存的不同部分进行实例化),因此trueResult == Boolean.TRUE的结果为false

相关问题