在之前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 也拿掉了,可能还得 publicArrayList<String> other = new ArrayList<>();other.add("world");nameField.set(oneCity,other );System.out.println("反射后:" + oneCity.getValue());} catch (NoSuchFieldException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (Exception e) {// TODO Auto-generated catch blocke.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 soFieldAccessor tmp = null;if (root != null) tmp = root.getFieldAccessor(overrideFinalCheck);if (tmp != null) {if (overrideFinalCheck)overrideFieldAccessor = tmp;elsefieldAccessor = tmp;} else {// Otherwise fabricate one and propagate it up to the roottmp = 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其实是修改了字段在内存中的值,所以编译器规则失效。

final 变量可以修改相关推荐

  1. Java forEach中 Lambda Expr中的 final变量要求

    https://my.oschina.net/wadelau/blog/1859419 Java forEach中 Lambda Expr中的 final变量要求 Java8闭包 闭包是一个函数在创建 ...

  2. [转载] Java中的final变量、final方法和final类

    参考链接: Java中的final数组 | Final arrays 1.final变量 final关键字可用于变量声明,一旦该变量被设定,就不可以再改变该变量的值.通常,由final定义的变量为常量 ...

  3. java中为final变量赋值的几种方式

    java中为final变量赋值的几种方式 前言 使用final修饰变量,很多人第一时间想到的就是不可变.然后以为变量必须得在声明的时候就为其赋初始值,其实不然,本文将详细讲解java中使用final修 ...

  4. java final 变量只读_java final的使用总结

    final 变量:是只读的: final 方法:是不能继承或者重写的. final 引用:引用不能修改,但是对象本身的属性可以修改: final class:不可继承: final MyObject ...

  5. Java面向对象-final类和final方法、final变量(常量)

    为什么得需要使用final修饰符 继承关系最大的弊端是破坏封装:子类能访问父类的实现细节,而且可以通过方法覆盖的形式修改实现细节. final本身的含义是"最终的,不可改变的",它 ...

  6. final方法、final变量、final类、final对象—Java

    final方法:表示方法不可被子类重写(覆盖) final变量:初始化一次后值不可变 final类:类不能被继承,内部的方法和变量都变成final类型 final对象:指对象的引用不可变,但是对象的值 ...

  7. Java中final变量的初始化方式

    原文转自:http://blog.csdn.net/zhangjk1993/article/details/24196847 1 public class FinalTest1 { 2 //----- ...

  8. Final变量的含义

    许多语言都有"固定不变的存储区"的概念,在C++中用CONST表示,在java中则用final表示,当fianl用于修饰一个变量时,不管该变量是一个类的成员还是一个临时的变量这个变 ...

  9. KG—Linux添加新的环境变量以及对PATH环境变量的修改

    KG的意思就是个"扩展"~~ 今天看了看Linux对环境变量的操作,感觉还是挺重要的,对于一些想偷懒的来说,那就是再好不过了~~ 所以呢,对于我,那就是更好不过了~~ 我这里只是说 ...

最新文章

  1. c++ 模板教程(c语言中文网) 自己运行实例
  2. DotNet(C#)自定义运行时窗体设计器 一
  3. nodejs和ionic小助手
  4. zookeeper3.5.x版本启动报错java.io.IOException: No snapshot found, but there are log entries.解决
  5. Qt——P12 信号连接信号
  6. python深度学习库系列教程——python调用opencv库教程
  7. 云服务器可以用来做什么?有什么用途?
  8. Atitit webserver web服务器的艺术 目录 1.1. 2.2 使用处理器处理请求 1 2. 2.5 处理器的作用域 : 2 2.1. 在Jetty中,很多标准的服务器会继承Handl
  9. 没有IOMMU的DMA操作
  10. Must specify unique android:id, android:tag, or have a parent with an id for 异常
  11. 【观察】戴尔:为核心数据“保驾护航”,为数字化转型“拨云见日”
  12. 软件全屏使用时点击鼠标自动跳回桌面的问题
  13. New Year Snowmen(贪心)
  14. python实现时间序列预处理
  15. AutoGPT保姆级安装使用教程
  16. Eclipse 里Tomcat 启动很慢
  17. 充电桩APP开发方案
  18. Java基础—char类型数据
  19. 再见Dubbo,不学会新的Java开发框架。你以为阿里P7能这么好拿?
  20. 大数据平台架构设计案例

热门文章

  1. 《指数基金投资指南》读书笔记---行业指数基金
  2. 轻松搞定Linux环境变量
  3. 计算机毕业答辩程序无法运行,计算机专业毕业答辩程序
  4. BFC到底是什么?如何理解
  5. 第5章 8051单片机工作原理
  6. 五脏六腑等最喜欢的食物
  7. 第一周《人月神话》读书笔记-------黄志鹏
  8. CF1265E Beautiful Mirrors
  9. 【jzoj2182】羊羊吃草
  10. oracle修改数据文件