相关阅读
【小家java】java5新特性(简述十大新特性) 重要一跃
【小家java】java6新特性(简述十大新特性) 鸡肋升级
【小家java】java7新特性(简述八大新特性) 不温不火
【小家java】java8新特性(简述十大新特性) 饱受赞誉
【小家java】java9新特性(简述十大新特性) 褒贬不一
【小家java】java10新特性(简述十大新特性) 小步迭代
【小家java】java11新特性(简述八大新特性) 首个重磅LTS版本
每篇一句
穷不练酒,富不占赌
1、概述
这可能是大家的一个共识:如果我们希望这个变量不可变,我们可以用final进行修饰。但本篇将带你深入了解不变的含义,我相信可以让你更深的了解final的原理,也能记得更牢靠
2、栗子
被final修饰过的变量,只是说栈存储的地址不能再改变,但是却没有说地址指向的内容不能改变。所以用final修饰,但内容是个对象啥的,然后改变对象属性值,这个不在本文讨论的范围以内。本文想讨论的是,直接就概念final的栈的地址,让它去指向另外一块内存地址。比如下面直接暴力反射处理,显然是不好使的:
private static final String str = "abc"; private final String str2 = "efg"; @Test public void fun1() throws Exception { System.out.println(str); //abc System.out.println(str2); //efg /// Field field = Tests.class.getDeclaredField("str"); field.setAccessible(true); field.set(this, "cba"); //java.lang.IllegalAccessException: Can not set static final System.out.println(str); }
接下来,就好好看看我是怎么改变这个值的:
private static final String str = "abc"; private final String str2 = "efg"; @Test public void fun1() throws Exception { System.out.println(str); //abc System.out.println(str2); //efg /// Field field = Tests.class.getDeclaredField("str2"); field.setAccessible(true); Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); field.set(this, "gfe"); System.out.println(str2); //efg }
诧异吧,我们会发现最后输出的还是efg,啪啪打脸啊,但是我debug一下,看到是如下情况:
这里面我解释两个东西:
1、为什么能够撼动final的值?
field.getModifiers()&~Modifier.FINAL 这句话就是去掉final。其实java的访问权限信息啥的都是以2的N次幂来作为表示的,具体都是在java.lang.reflect.Modifier这个类里。so,咱们都把它的修饰符干掉,当然可以对Field set值了
所以,java的反射机制直接打破了封装有木有,哈哈哈
2、为什么最终打印的和我们调试的值不一样?
System.out.println(str2); //efg System.out.println(field.get(this)); //gfe 通过反射拿到的值是对的
我们通过反射拿到的值是正确的,而直接输出变量的值却是不对的。究其原因:这其实是Java编译器对 final 属型的内联优化(java的内联机制和jvm底层有关,对程序调优有非常重要的作用。后续JVM相关博文,我会重点讨论),即编译时把该 final 的值直接放到了引用它的地方。即使是反射修改了该属性,但这种事后处理于事无补。
所以,咱们确实是可以通过反射来修改final的值,但是我们在后续代码中却不能用,尴尬。为了解决这个问题,设计的面实在是有点多,所以此处不适合展开来说。等后面讲述了JVM相关的东西后,会回到这里补充,请持续关注。。。
但是,请大家可以记住一个结论:
只要不会被编译器内联优化的 final 属性就可以通过反射有效的进行修改 – 修改后代码中可使用到新的值
3、使用场景
几乎没啥使用场景,除非一些极限情况:比如强制修改第三方源码。。。
4、最后
整理出来的内容,希望能加深大家对final关键字的了解