有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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

据我所知,下面的代码应该打印"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 r1 and r2 be the results of any two boxing conversions of p. It is always the case that r1 == r2.

然而,在通过反射调用方法的情况下,装箱值总是通过new PrimitiveWrapper()创建的

请帮助我理解这一点


共 (4) 个答案

  1. # 1 楼答案

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

    ...if the [return] value has a primitive type, it is first appropriately wrapped in an object.

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

  2. # 2 楼答案

    一,

    具体

    in case of method called via reflection

    不在您所引用的JLS部分中。你引用的部分是关于类型转换,当你有一个类型的值作为另一个类型传递时。在这里,您正在考虑将布尔值转换为布尔值

    但是类型转换意味着做类似的事情:

    Boolean b = true;
    

    boolean b = true;
    Boolean b2 = b;
    

    反射不是应用类型转换的机制

    当反射方法调用需要将布尔返回值包装到布尔对象中时,您引用的JLS部分不涉及它

    这就解释了为什么这里没有违反JLS

      2.

    至于为什么反射没有选择与此行为保持一致:

    这是因为在旧版本的Java中,反射在泛型之前就存在了。泛型是自动装箱突然变得方便的原因,而自动装箱是不复制包装原语的“公共”值似乎很明智的原因

    所有这些都是在反射已经存在一段时间之后定义的,并且已经以一种特定的方式表现出来。这意味着已经存在使用反射的Java代码,并且很可能存在一些错误地依赖于现有行为的现有代码。更改现有行为会破坏现有代码,因此避免了这种情况

  3. # 3 楼答案

    Is caching of boxed Byte objects not required by Java 13 SE spec?中所述,引用的部分已重写多次

    您已经引用了使用到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代码更难更改。但自JDK1.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让反射立即使用字节码

  4. # 4 楼答案

    正如您在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