我是靠谱客的博主 自由鸡,这篇文章主要介绍final 变量可以修改,现在分享给大家,希望可以做个参考。

在之前java 开发的认知中,final 修饰的变量一旦初始化,就不能被修改,如果是类变量,只能在构造方法中初始化,在其他方法中如果初始化,编译器也会报错,IDE也会拒绝编译。如下:

这个没问题,这是所有开发者的共识,但是如果遇到了反射,会有些不同,如下:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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 方法的源码如下

复制代码
1
2
3
4
5
6
7
8
9
10
11
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()的源码如下

复制代码
1
2
3
4
5
6
7
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,

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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源码如下:

复制代码
1
2
3
4
5
public FieldAccessor newFieldAccessor(Field var1, boolean var2) { checkInitted(); return UnsafeFieldAccessorFactory.newFieldAccessor(var1, var2); }

跟中下去,因为方法比较长,不贴了,实际返回的是

复制代码
1
2
UnsafeQualifiedObjectFieldAccessorImpl的对象,

这样返回到Field.set,实际就是调用UnsafeQualifiedObjectFieldAccessorImpl的set,源码如下:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
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

复制代码
1
public native void putObject(Object var1, long var2, Object var4);

是个native 方法,在Unsafe.cpp中实现,真正实现的是Unsafe_SetObjectVolatile方法

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
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其实是修改了字段在内存中的值,所以编译器规则失效。

 

 

最后

以上就是自由鸡最近收集整理的关于final 变量可以修改的全部内容,更多相关final内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(53)

评论列表共有 0 条评论

立即
投稿
返回
顶部