final 修饰的变量一定不能被修改?一定能被修改?

2024-08-22 15:36

本文主要是介绍final 修饰的变量一定不能被修改?一定能被修改?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

1.final 修饰成员属性

2.final 修饰局部变量


1.final 修饰成员属性

1.1 final 修饰 String 类型的成员属性

public class FinalReflection {private static final String FINAL_STRING = "张三";public static void main(String[] args) throws Exception {System.out.println("反射前: " + FINAL_STRING);Class<FinalReflection> clazz = FinalReflection.class;Field field = clazz.getDeclaredField("FINAL_STRING");field.setAccessible(true);// 取消 final 修饰符的效果Field modifiersField = Field.class.getDeclaredField("modifiers");modifiersField.setAccessible(true);modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL);// 修改 final 的值field.set(null, "李四");// 使用反射再次获取字段的值System.out.println("通过反射获取修改后的值: " + field.get(null));System.out.println("直接访问 FINAL_STRING: " + FINAL_STRING);}
}

程序执行结果:

在 Java 中,final 关键字用于声明常量,它使得变量一旦赋值后就不能被修改。不过,通过反射机制,可以在特殊情况下修改 final 变量的值。

此处通过反射来获取值,确实看到修改了,只不过 FINAL_STRING 是一个 static final 变量,且它的值在编译时已经确定。对于这种情况,Java 编译器会进行优化,将使用 FINAL_STRING 的地方直接替换为它的值 "张三"(直接引用替换符号引用)。这意味着在编译后的字节码中,直接存储了 "张三" 这个值,而不是通过访问变量 FINAL_STRING 来获取它,所以直接访问 FINAL_STRING是看不到修改的。

1.2 final 修饰 Integer 类型的成员属性

public class Example {private static final Integer NUM = 128;public static void main(String[] args) throws Exception {// 这里的值已经被编译器内联为 5System.out.println(NUM);// 通过反射修改 x// 报错:java.lang.IllegalAccessException: Can not set final int fieldField field = Example.class.getDeclaredField("NUM");field.setAccessible(true);field.setInt(null, 10);System.out.println(NUM);}
}

程序执行结果: 

为啥前面修改 final String 还好好的,只不过编译器做了优化,导致它的指向并没有被改变,而到 final Integer 就直接报错了?

1.3 比较 “Integer” 和 “String”

Integer 的反射行为

  • 缓存机制Integer-128127 范围内使用缓存机制。final 修饰符的值不会被轻易修改,因为修改会影响到缓存和内部的优化机制。对于超出这个范围的值,创建新对象时可能会抛出异常或编译错误

  • 编译优化:对于 final 修饰的 Integer,编译器在编译时可能会对值进行优化,使得直接修改变得不现实。

String 的反射行为

  • 常量池String 在编译时会利用常量池进行优化。当 String 变量被声明为 final 时,编译器可能会将其内联到常量池中。在这种情况下,即使通过反射尝试修改 final 变量的引用,实际的编译器优化可能会防止这种修改生效。

  • 反射的影响:由于 String 对象的不可变性和常量池的优化,尝试通过反射修改 final 变量的引用不会直接导致编译错误,但修改后的结果可能不会被反映出。

2.final 修饰局部变量

修改 final 修饰 Integer 的局部变量:

public class Example {public static void main(String[] args) throws Exception {final Integer num = 128;// 通过反射修改 xField field = Example.class.getDeclaredField("num");field.setAccessible(true);field.setInt(null, 10);System.out.println(num);}
}

修改 final 修饰 String 的局部变量:

public class FinalReflection {public static void main(String[] args) throws Exception {final String FINAL_STRING = "张三";System.out.println("反射前: " + FINAL_STRING);Class<FinalReflection> clazz = FinalReflection.class;Field field = clazz.getDeclaredField("FINAL_STRING");field.setAccessible(true);// 取消 final 修饰符的效果Field modifiersField = Field.class.getDeclaredField("modifiers");modifiersField.setAccessible(true);modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL);// 修改 final 的值field.set(null, "李四");// 使用反射再次获取字段的值System.out.println("通过反射获取修改后的值: " + field.get(null));System.out.println("直接访问 FINAL_STRING: " + FINAL_STRING);}
}

以上两种情况,代码都会报错,所以 final 修饰的局部变量是不允许被修改的!

编译时内联

  • 局部变量:编译器在优化时可能将 final 局部变量内联到代码中,这意味着代码在运行时直接使用变量的值,而不是通过引用获取值。
  • 成员变量:即使是 final 成员变量,也可能在编译时进行优化,但这主要是指编译器将其常量值内联到字节码中。在运行时,成员变量仍然是对象的一部分,可以通过反射进行操作。

实际效果

即使你通过反射成功修改了 final 成员变量的值,这种修改在实际应用中可能不会如预期那样生效。原因包括:

  • JVM 缓存和优化:JVM 可能对 final 字段应用了各种优化措施,例如缓存字段值,这使得反射修改可能不会立即反映在应用程序中。
  • 编译器优化:编译器可能会对 final 字段做出优化,使得即使你通过反射修改了字段值,程序仍然会看到原始的常量值。

这篇关于final 修饰的变量一定不能被修改?一定能被修改?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1096683

相关文章

变量与命名

引言         在前两个课时中,我们已经了解了 Python 程序的基本结构,学习了如何正确地使用缩进来组织代码,并且知道了注释的重要性。现在我们将进一步深入到 Python 编程的核心——变量与命名。变量是我们存储数据的主要方式,而合理的命名则有助于提高代码的可读性和可维护性。 变量的概念与使用         在 Python 中,变量是一种用来存储数据值的标识符。创建变量很简单,

两个月冲刺软考——访问位与修改位的题型(淘汰哪一页);内聚的类型;关于码制的知识点;地址映射的相关内容

1.访问位与修改位的题型(淘汰哪一页) 访问位:为1时表示在内存期间被访问过,为0时表示未被访问;修改位:为1时表示该页面自从被装入内存后被修改过,为0时表示未修改过。 置换页面时,最先置换访问位和修改位为00的,其次是01(没被访问但被修改过)的,之后是10(被访问了但没被修改过),最后是11。 2.内聚的类型 功能内聚:完成一个单一功能,各个部分协同工作,缺一不可。 顺序内聚:

如何在运行时修改serialVersionUID

优质博文:IT-BLOG-CN 问题 我正在使用第三方库连接到外部系统,一切运行正常,但突然出现序列化错误 java.io.InvalidClassException: com.essbase.api.base.EssException; local class incompatible: stream classdesc serialVersionUID = 90314637791991

解决Office Word不能切换中文输入

我们在使用WORD的时可能会经常碰到WORD中无法输入中文的情况。因为,虽然我们安装了搜狗输入法,但是到我们在WORD中使用搜狗的输入法的切换中英文的按键的时候会发现根本没有效果,无法将输入法切换成中文的。下面我就介绍一下如何在WORD中把搜狗输入法切换到中文。

android系统源码12 修改默认桌面壁纸--SRO方式

1、aosp12修改默认桌面壁纸 代码路径 :frameworks\base\core\res\res\drawable-nodpi 替换成自己的图片即可,不过需要覆盖所有目录下的图片。 由于是静态修改,则需要make一下,重新编译。 2、方法二Overlay方式 由于上述方法有很大缺点,修改多了之后容易遗忘自己修改哪些文件,为此我们采用另外一种方法,使用Overlay方式。

【经验交流】修复系统事件查看器启动不能时出现的4201错误

方法1,取得『%SystemRoot%\LogFiles』文件夹和『%SystemRoot%\System32\wbem』文件夹的权限(包括这两个文件夹的所有子文件夹的权限),简单点说,就是使你当前的帐户拥有这两个文件夹以及它们的子文件夹的绝对控制权限。这是最简单的方法,不少老外说,这样一弄,倒是解决了问题。不过对我的系统,没用; 方法2,以不带网络的安全模式启动,运行命令行,输入“ne

JS_变量

二、JS的变量 JS中的变量具有如下特征 1 弱类型变量,可以统一声明成var 2 var声明的变量可以再次声明 3 变量可以使用不同的数据类型多次赋值 4 JS的语句可以以; 结尾,也可以不用;结尾 5 变量标识符严格区分大小写 6 标识符的命名规则参照JAVA 7 如果使用了 一个没有声明的变量,那么运行时会报uncaught ReferenceError: *** is not de

hibernate修改数据库已有的对象【简化操作】

陈科肇 直接上代码: /*** 更新新的数据并并未修改旧的数据* @param oldEntity 数据库存在的实体* @param newEntity 更改后的实体* @throws IllegalAccessException * @throws IllegalArgumentException */public void updateNew(T oldEntity,T newEntity

跟我一起玩《linux内核设计的艺术》第1章(四)——from setup.s to head.s,这回一定让main滚出来!(已解封)

看到书上1.3的大标题,以为马上就要见着main了,其实啊,还早着呢,光看setup.s和head.s的代码量就知道,跟bootsect.s没有可比性,真多……这确实需要包括我在内的大家多一些耐心,相信见着main后,大家的信心和干劲会上一个台阶,加油! 既然上篇已经玩转gdb,接下来的讲解肯定是边调试边分析书上的内容,纯理论讲解其实我并不在行。 setup.s: 目标:争取把setup.

SW - 引入第三方dwg图纸后,修改坐标原点

文章目录 SW - 引入第三方dwg图纸后,修改坐标原点概述笔记设置图纸新原点END SW - 引入第三方dwg图纸后,修改坐标原点 概述 在solidworks中引入第三方的dwg格式图纸后,坐标原点大概率都不合适。 全图自动缩放后,引入的图纸离默认的原点位置差很多。 需要自己重新设置原点位置,才能自动缩放后,在工作区中间显示引入的图纸。 笔记 将dwg图纸拖到SW中