当final修饰一个数据域时,意义是声明该数据域是最终的,不可修改的。常见的使用场景就是eclipse自动生成的serialVersionUID一般都是final的。

另外还可以构造线程安全(thread safe)的immutable类,比如String,其数据域都是final的。这些使用场景都建立在final不可修改这个条件上,但是,反射可以打破这一切。

1.利用反射修改final数据域

首先,构造一个Person类,里面有个final字段NAME。我们尝试着修改这个字段。顺利的出乎意料。

public class Person {

    public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
Person p = new Person();
Field field = p.getClass().getDeclaredField("NAME");
field.setAccessible(true);
field.set(p,"Hello");
System.out.println(field.get(p));
//p.printName();
} private final String NAME = "Clive";
public Person() { }
public void printName() {
System.out.println(NAME);
}
}
/***************
console print:
Hello
***************/

2.内联与内联消除

NAME数据域如此简单的就被修改了,final真是太"不安全了"! 但是,当我们调用p.printName() 时,控制台打印的却是"Clive"字符串。这是因为JVM做了优化处理, 当一个数据域被final修饰,那就表明这个数据域是常量,JVM会把所有NAME数据域出现的地方全部用"Clive"替换掉, 比如 printName() 方法其实被优化成了这样。

public void printName() { System.out.println("Clive"); }

所以,要想不被自动优化,就要把代码弄得复杂点,如下

public class Person {

    public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
Person p = new Person();
Field field = p.getClass().getDeclaredField("NAME");
field.setAccessible(true);
field.set(p,"Hello");
System.out.println(field.get(p));
p.printName();
} private final String NAME =(null!=null?"Clive":"Clive"); //声明时即初始化
public Person() {
//或者,在这里设置NAME数据域的值
//NAME="Clive";
}
public void printName() {
System.out.println(NAME);
}
}
/***************
console print:
Hello
Hello
***************/

结果见 console print,顺利消除了优化,final字段最终被修改了!

3.修改static final数据域

如果在NAME字段再增加一个static关键字修饰,然后再用反射修改的话就不行了, 会抛出异常

java.lang.IllegalAccessException: Can not set static final int field ...

这时,修改Field中的modifiers数据域,清除代表final的那个bit,才可以成功修改。

public class Person {

    public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
Person p = new Person();
Field field = p.getClass().getDeclaredField("NAME");
Field modifiers = field.getClass().getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);//fianl标志位置0
field.set(p,"Hello");
System.out.println(field.get(p));
p.printName();
} private final String NAME =(null!=null?"Clive":"Clive");
public Person() {
}
public void printName() {
System.out.println(NAME);
}
}
/**************
console print:
Hello
Hello
**************/

总结

这个知识点感觉知道就好,平时还是不要修改final数据域的好 :)

引用

1.https://www.oschina.net/question/1245392_159103

2.https://github.com/jOOQ/jOOR

最新文章

  1. 自我反思--table的简单数据分页
  2. mysql主从切换摘要
  3. 分形树Fractal tree介绍——具体如何结合TokuDB还没有太懂,先记住其和LSM都是一样的适合写密集
  4. 知道创宇爬虫题--代码持续更新中 - littlethunder的专栏 - 博客频道 - CSDN.NET
  5. Chapter 1 First Sight——24
  6. 【死磕Java并发】-----Java内存模型之happend-before
  7. 深刻认识shift_ram IP core——图像处理学习笔记
  8. BZOJ 2456: mode(新生必做的水题)
  9. goodsSearch初始化选中代码
  10. pip3 install mysqlclient 报错 “/bin/sh: 1: mysql_config: not found”的解决方法
  11. C++对象的内存分布和虚函数表
  12. 构建RESTful API(十八)
  13. nginx配置文件nginx.conf 不包括server节点
  14. Java——复制txt文件
  15. Rhythmk 一步一步学 JAVA (22) JAVA 网络编程
  16. jenkins任务构建失败重试插件Naginator Plugin
  17. [javaSE] 集合工具类(Collections-sort)
  18. openlayers研究(一) 初始化流程
  19. 《深入浅出MyBatis技术原理与实战》——6. MyBatis的解析和运行原理
  20. 零基础学习Vim编辑器

热门文章

  1. 实现一个clone函数,对javascript中的5种数据类型进行值复制
  2. C# CheckBoxList 实现全选/反选功能怎么写?
  3. Spring 中IOC(控制反转)&& 通过SET方式为属性注入值 && Spring表达式
  4. Springcloud Eureka 启动失败:ERROR org.springframework.boot.SpringApplication - Application run failed
  5. 给网站添加icon图标
  6. swoole学习(一)----linux安装swoole
  7. 解决scp命令pemission denied,please try again的问题
  8. 多线程之ReadWriteLock模拟缓存(九)
  9. 交叉编译qt5.6
  10. C语言数组篇(四)二维数组