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

相关文章

Java 接口定义变量的示例代码

《Java接口定义变量的示例代码》文章介绍了Java接口中的变量和方法,接口中的变量必须是publicstaticfinal的,用于定义常量,而方法默认是publicabstract的,必须由实现类... 在 Java 中,接口是一种抽象类型,用于定义类必须实现的方法。接口可以包含常量和方法,但不能包含实例

MySQL底层文件的查看和修改方法

《MySQL底层文件的查看和修改方法》MySQL底层文件分为文本类(可安全查看/修改)和二进制类(禁止手动操作),以下按「查看方法、修改方法、风险管控三部分详细说明,所有操作均以Linux环境为例,需... 目录引言一、mysql 底层文件的查看方法1. 先定位核心文件路径(基础前提)2. 文本类文件(可直

Nginx内置变量应用场景分析

《Nginx内置变量应用场景分析》Nginx内置变量速查表,涵盖请求URI、客户端信息、服务器信息、文件路径、响应与性能等类别,这篇文章给大家介绍Nginx内置变量应用场景分析,感兴趣的朋友跟随小编一... 目录1. Nginx 内置变量速查表2. 核心变量详解与应用场景3. 实际应用举例4. 注意事项Ng

kingbase修改权限实现方式

《kingbase修改权限实现方式》该文章详细介绍了如何在数据库中创建用户并赋予其相应的权限,包括创建用户、回收默认权限、创建数据库、赋权数据库权限、创建只读用户以及回收权限等步骤... 目录前言使用步骤总结前言创建用户后对数据库对象的读写权限进行修改使用步骤1、创建用户create user cs

linux实现对.jar文件的配置文件进行修改

《linux实现对.jar文件的配置文件进行修改》文章讲述了如何使用Linux系统修改.jar文件的配置文件,包括进入文件夹、编辑文件、保存并退出编辑器,以及重新启动项目... 目录linux对.jar文件的配置文件进行修改第一步第二步 第三步第四步总结linux对.jar文件的配置文件进行修改第一步进

Python实现Word文档自动化的操作大全(批量生成、模板填充与内容修改)

《Python实现Word文档自动化的操作大全(批量生成、模板填充与内容修改)》在职场中,Word文档是公认的好伙伴,但你有没有被它折磨过?批量生成合同、制作报告以及发放证书/通知等等,这些重复、低效... 目录重复性文档制作,手动填充模板,效率低下还易错1.python-docx入门:Word文档的“瑞士

Python之变量命名规则详解

《Python之变量命名规则详解》Python变量命名需遵守语法规范(字母开头、不使用关键字),遵循三要(自解释、明确功能)和三不要(避免缩写、语法错误、滥用下划线)原则,确保代码易读易维护... 目录1. 硬性规则2. “三要” 原则2.1. 要体现变量的 “实际作用”,拒绝 “无意义命名”2.2. 要让

Python函数的基本用法、返回值特性、全局变量修改及异常处理技巧

《Python函数的基本用法、返回值特性、全局变量修改及异常处理技巧》本文将通过实际代码示例,深入讲解Python函数的基本用法、返回值特性、全局变量修改以及异常处理技巧,感兴趣的朋友跟随小编一起看看... 目录一、python函数定义与调用1.1 基本函数定义1.2 函数调用二、函数返回值详解2.1 有返

Nginx屏蔽服务器名称与版本信息方式(源码级修改)

《Nginx屏蔽服务器名称与版本信息方式(源码级修改)》本文详解如何通过源码修改Nginx1.25.4,移除Server响应头中的服务类型和版本信息,以增强安全性,需重新配置、编译、安装,升级时需重复... 目录一、背景与目的二、适用版本三、操作步骤修改源码文件四、后续操作提示五、注意事项六、总结一、背景与

SpringBoot中@Value注入静态变量方式

《SpringBoot中@Value注入静态变量方式》SpringBoot中静态变量无法直接用@Value注入,需通过setter方法,@Value(${})从属性文件获取值,@Value(#{})用... 目录项目场景解决方案注解说明1、@Value("${}")使用示例2、@Value("#{}"php