在反射中,想要为一个实例域设置值,需要调用相关的set方法
但在The Java Tutorials中的教程我们知道反射时可能会因为不能自动拆箱装箱导致IllegalArgumentException。

public class A {

    public static void main(String[] args) {
        try {
            B b = new B();
            Field f = B.class.getDeclaredField("i");
            f.setAccessible(true);
            f.setInt(b,30);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }

    }
}

class B {
    private Integer i;
}
// 输出
Exception in thread "main" java.lang.IllegalArgumentException

java 阈值范围转开闭区间 java阈值设置_box


发现异常是在

at sun.reflect.UnsafeObjectFieldAccessorImpl.setInt(UnsafeObjectFieldAccessorImpl.java:114)

我们跟踪以一下f.set()方法的运行过程

java 阈值范围转开闭区间 java阈值设置_box_02


接着进入方法getFieldAccessor()

java 阈值范围转开闭区间 java阈值设置_box_03


接着进入方法acquireFieldAccessor()

java 阈值范围转开闭区间 java阈值设置_反射_04


然后回到set()方法

java 阈值范围转开闭区间 java阈值设置_java_05

再进入setInt()方法

java 阈值范围转开闭区间 java阈值设置_java_06


发现在这个文件中,只有set()方法有可能不抛出异常,因为这是UnsafObjectField,Interger实例域实际上是一个Object对象,所以f.setInt()方法中getFieldAccessor()返回了一个UnsafeObjectFieldAccessorImpl对象。Java没有为自定义类和预定义类定义相应的类型,所有引用类型(除了基本类型的类型)都会返回UnsafeObjectField,但是每个基本类型都有对应的UnsafeLong,UnsafeBoolea, UnsafeInteger…

java 阈值范围转开闭区间 java阈值设置_box_07


所以当想要设置的域不是基本类型时,一律用set()方法,即使该实例域的类型时基本类型的包装类。

下面将想要修稿的实例域的类型改为基本类型,再次跟踪一遍。

先调用setInt()方法

java 阈值范围转开闭区间 java阈值设置_debug_08


发现返回的就是UnsafeIntegerField

再次进入setInt()方法

java 阈值范围转开闭区间 java阈值设置_java 阈值范围转开闭区间_09


发现再UnsafeIntegerFieldAccessorImpl中setINt就不再抛出异常SetIllegalArgumentException。

进入putInt()方法,是个native方法,没有找到源码,搜索后,在Open JDK 中发现

public native  void putInt(Object o,
    long offset,
    int x)

Stores a value into a given Java variable.
The first two parameters are interpreted exactly as with
#getInt(Object, long) to refer to a specific
Java variable (field or array element). The given value
is stored into that variable.
The variable must be of the same type as the method
parameter x.

下面在对基本类型域(以int为例)调用set()方法,看看会出现什么结果。

还是返回的UnsafeIntegerField

java 阈值范围转开闭区间 java阈值设置_box_10


进入set()方法:

java 阈值范围转开闭区间 java阈值设置_debug_11


发现UnsafeIntegerFieldAccesorImpl类中的set()方法除了抛出异常,同样可以设置值。

进入unsafe.putInt()方法后,发现跳转到了Integer类中的intValue()方法

java 阈值范围转开闭区间 java阈值设置_java_12


说明java编译器多运行了一个方法进行了类似于拆箱的操作。

所以,对基本类型的域也可以调用set()方法,但是比直接调用setInt()的性能更差。

综上所述:

  • 当想要设置的域不是基本类型时,一律用set()方法,即使该实例域的类型时基本类型的包装类。
  • 对基本类型的域既可以调用setInt()方法也可以调用set()方法,但是调用set()方法的性能更差。