目录
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 {
// 这里的值已经被编译器内联为 5
System.out.println(NUM);
// 通过反射修改 x
// 报错:java.lang.IllegalAccessException: Can not set final int field
Field 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
在-128
到127
范围内使用缓存机制。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;
// 通过反射修改 x
Field 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
字段做出优化,使得即使你通过反射修改了字段值,程序仍然会看到原始的常量值。