【小家java】final修饰的变量真的不可变吗?

相关阅读

【小家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关键字的了解


关注A哥

Author A哥(YourBatman)
个人站点 www.yourbatman.cn
E-mail [email protected]
微 信 fsx641385712
活跃平台
公众号 BAT的乌托邦(ID:BAT-utopia)
知识星球 BAT的乌托邦
每日文章推荐 每日文章推荐

你可能感兴趣的:(享学Java)