在之前java 开发的认知中,final 修饰的变量一旦初始化,就不能被修改,如果是类变量,只能在构造方法中初始化,在其他方法中如果初始化,编译器也会报错,IDE也会拒绝编译。如下:
这个没问题,这是所有开发者的共识,但是如果遇到了反射,会有些不同,如下:
public class OneCity {
private final ArrayList<String> names;
public OneCity() {
names = new ArrayList<>();
names.add("hello");
}
public String getValue() {
return names.toString();
}
}
public class TestString {
public static void main(String[] args) {
OneCity oneCity = new OneCity();
System.out.println("反射前:" + oneCity.getValue());
try {
Field nameField;
nameField = OneCity.class.getDeclaredField("names");
nameField.setAccessible(true); // 这个同样不能少,除非上面把 private 也拿掉了,可能还得 public
ArrayList<String> other = new ArrayList<>();
other.add("world");
nameField.set(oneCity,other );
System.out.println("反射后:" + oneCity.getValue());
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
输出的是:
反射前:[hello]
反射后:[world]
显然oneCity的names被替换了,这让我一脸懵逼,之前的认知瞬间被推翻,反射的威力实在太强大了,反射完美绕开了编译器的限制,那究竟背后藏了什么玄机,反射的威力这么强大呢,值得一探究竟。跟踪源码需要sun.reflect包,这个jdk源码没有包含,不过不要紧,sun.reflect包存在于jre/lib/rt.jar,反编译就可以,如果有源码的无所谓了。Field.set 方法的源码如下
public void set(Object obj, Object value)
throws IllegalArgumentException, IllegalAccessException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
getFieldAccessor(obj).set(obj, value);
}
可以看到调用了getFieldAccessor(obj)返回的FieldAccessor的set方法,这个FieldAccessor是个接口。getFieldAccessor()的源码如下
private FieldAccessor getFieldAccessor(Object obj)
throws IllegalAccessException
{
boolean ov = override;
FieldAccessor a = (ov) ? overrideFieldAccessor : fieldAccessor;
return (a != null) ? a : acquireFieldAccessor(ov);
}
初始时,overrideFieldAccessor和fieldAccessor都为null,变量a肯定null,接着调用acquireFieldAccessor,
private FieldAccessor acquireFieldAccessor(boolean overrideFinalCheck) {
// First check to see if one has been created yet, and take it
// if so
FieldAccessor tmp = null;
if (root != null) tmp = root.getFieldAccessor(overrideFinalCheck);
if (tmp != null) {
if (overrideFinalCheck)
overrideFieldAccessor = tmp;
else
fieldAccessor = tmp;
} else {
// Otherwise fabricate one and propagate it up to the root
tmp = reflectionFactory.newFieldAccessor(this, overrideFinalCheck);
setFieldAccessor(tmp, overrideFinalCheck);
}
return tmp;
}
核心代码就是 tmp = reflectionFactory.newFieldAccessor(this, overrideFinalCheck);reflectionFactory的类型是ReflectionFactory,我们查看,这时候反编译ReflectionFactory.class。reflectionFactory.newFieldAccessor源码如下:
public FieldAccessor newFieldAccessor(Field var1, boolean var2) {
checkInitted();
return UnsafeFieldAccessorFactory.newFieldAccessor(var1, var2);
}
跟中下去,因为方法比较长,不贴了,实际返回的是
UnsafeQualifiedObjectFieldAccessorImpl的对象,
这样返回到Field.set,实际就是调用UnsafeQualifiedObjectFieldAccessorImpl的set,源码如下:
public void set(Object var1, Object var2) throws IllegalArgumentException, IllegalAccessException {
this.ensureObj(var1);
if (this.isReadOnly) {
this.throwFinalFieldIllegalAccessException(var2);
}
if (var2 != null && !this.field.getType().isAssignableFrom(var2.getClass())) {
this.throwSetIllegalArgumentException(var2);
}
unsafe.putObjectVolatile(var1, this.fieldOffset, var2);
}
关键代码是unsafe.putObjectVolatile,unsafe就是大名鼎鼎的Unsafe的对象,熟悉Unsafe的都知道,它可以直接访问系统内存资源,putObjectVolatile,这个方法名很奇怪,感觉和volatile有关系,先不管,这篇分析两者关系,https://blog.csdn.net/hanshengjian/article/details/86612767
public native void putObject(Object var1, long var2, Object var4);
是个native 方法,在Unsafe.cpp中实现,真正实现的是Unsafe_SetObjectVolatile方法
UNSAFE_ENTRY(void, Unsafe_SetObjectVolatile(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jobject x_h))
UnsafeWrapper("Unsafe_SetObjectVolatile");
oop x = JNIHandles::resolve(x_h);
oop p = JNIHandles::resolve(obj);
void* addr = index_oop_from_field_offset_long(p, offset);
OrderAccess::release();
if (UseCompressedOops) {
oop_store((narrowOop*)addr, x);
} else {
oop_store((oop*)addr, x);
}
OrderAccess::fence();
UNSAFE_END
核心就是调用了oop_store方法,从方法意思就是替换偏移量offset的字段指向更新为传入的x_h对象,这个x_h就是Field.set传入的第二个参数。
总结一下:Field.set其实是修改了字段在内存中的值,所以编译器规则失效。