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实现无痛修改第三方库源码的方法详解》很多时候,我们下载的第三方库是不会有需求不满足的情况,但也有极少的情况,第三方库没有兼顾到需求,本文将介绍几个修改源码的操作,大家可以根据需求进行选择... 目录需求不符合模拟示例 1. 修改源文件2. 继承修改3. 猴子补丁4. 追踪局部变量需求不符合很

Linux修改pip和conda缓存路径的几种方法

《Linux修改pip和conda缓存路径的几种方法》在Python生态中,pip和conda是两种常见的软件包管理工具,它们在安装、更新和卸载软件包时都会使用缓存来提高效率,适当地修改它们的缓存路径... 目录一、pip 和 conda 的缓存机制1. pip 的缓存机制默认缓存路径2. conda 的缓

Linux修改pip临时目录方法的详解

《Linux修改pip临时目录方法的详解》在Linux系统中,pip在安装Python包时会使用临时目录(TMPDIR),但默认的临时目录可能会受到存储空间不足或权限问题的影响,所以本文将详细介绍如何... 目录引言一、为什么要修改 pip 的临时目录?1. 解决存储空间不足的问题2. 解决权限问题3. 提

Linux文件名修改方法大全

《Linux文件名修改方法大全》在Linux系统中,文件名修改是一个常见且重要的操作,文件名修改可以更好地管理文件和文件夹,使其更具可读性和有序性,本文将介绍三种在Linux系统下常用的文件名修改方法... 目录一、引言二、使用mv命令修改文件名三、使用rename命令修改文件名四、mv命令和rename命

mybatis-plus 实现查询表名动态修改的示例代码

《mybatis-plus实现查询表名动态修改的示例代码》通过MyBatis-Plus实现表名的动态替换,根据配置或入参选择不同的表,本文主要介绍了mybatis-plus实现查询表名动态修改的示... 目录实现数据库初始化依赖包配置读取类设置 myBATis-plus 插件测试通过 mybatis-plu

Linux下修改hostname的三种实现方式

《Linux下修改hostname的三种实现方式》:本文主要介绍Linux下修改hostname的三种实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下修改ho编程stname三种方式方法1:修改配置文件方法2:hFvEWEostnamectl命

如何关闭 Mac 触发角功能或设置修饰键? mac电脑防止误触设置技巧

《如何关闭Mac触发角功能或设置修饰键?mac电脑防止误触设置技巧》从Windows换到iOS大半年来,触发角是我觉得值得吹爆的MacBook效率神器,成为一大说服理由,下面我们就来看看mac电... MAC 的「触发角」功能虽然提高了效率,但过于灵敏也让不少用户感到头疼。特别是在关键时刻,一不小心就可能触

Git如何修改已提交人的用户名和邮箱

《Git如何修改已提交人的用户名和邮箱》文章介绍了如何修改Git已提交人的用户名和邮箱,包括注意事项和具体步骤,确保操作正确无误... 目录git修改已提交人的用户名和邮箱前言第一步第二步总结git修改已提交人的用户名和邮箱前言需注意以下两点内容:需要在顶层目录下(php就是 .git 文件夹所在的目

修改若依框架Token的过期时间问题

《修改若依框架Token的过期时间问题》本文介绍了如何修改若依框架中Token的过期时间,通过修改`application.yml`文件中的配置来实现,默认单位为分钟,希望此经验对大家有所帮助,也欢迎... 目录修改若依框架Token的过期时间修改Token的过期时间关闭Token的过期时js间总结修改若依

MySQL修改密码的四种实现方式

《MySQL修改密码的四种实现方式》文章主要介绍了如何使用命令行工具修改MySQL密码,包括使用`setpassword`命令和`mysqladmin`命令,此外,还详细描述了忘记密码时的处理方法,包... 目录mysql修改密码四种方式一、set password命令二、使用mysqladmin三、修改u