在反射中,想要为一个实例域设置值,需要调用相关的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
发现异常是在
at sun.reflect.UnsafeObjectFieldAccessorImpl.setInt(UnsafeObjectFieldAccessorImpl.java:114)
我们跟踪以一下f.set()方法的运行过程
接着进入方法getFieldAccessor()
接着进入方法acquireFieldAccessor()
然后回到set()方法
再进入setInt()方法
发现在这个文件中,只有set()方法有可能不抛出异常,因为这是UnsafObjectField,Interger实例域实际上是一个Object对象,所以f.setInt()方法中getFieldAccessor()返回了一个UnsafeObjectFieldAccessorImpl对象。Java没有为自定义类和预定义类定义相应的类型,所有引用类型(除了基本类型的类型)都会返回UnsafeObjectField,但是每个基本类型都有对应的UnsafeLong,UnsafeBoolea, UnsafeInteger…
所以当想要设置的域不是基本类型时,一律用set()方法,即使该实例域的类型时基本类型的包装类。
下面将想要修稿的实例域的类型改为基本类型,再次跟踪一遍。
先调用setInt()方法
发现返回的就是UnsafeIntegerField
再次进入setInt()方法
发现再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
进入set()方法:
发现UnsafeIntegerFieldAccesorImpl类中的set()方法除了抛出异常,同样可以设置值。
进入unsafe.putInt()方法后,发现跳转到了Integer类中的intValue()方法
说明java编译器多运行了一个方法进行了类似于拆箱的操作。
所以,对基本类型的域也可以调用set()方法,但是比直接调用setInt()的性能更差。
综上所述:
- 当想要设置的域不是基本类型时,一律用set()方法,即使该实例域的类型时基本类型的包装类。
- 对基本类型的域既可以调用setInt()方法也可以调用set()方法,但是调用set()方法的性能更差。