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 {
        // 这里的值已经被编译器内联为 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-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;
        
        // 通过反射修改 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 字段做出优化,使得即使你通过反射修改了字段值,程序仍然会看到原始的常量值。
  • 14
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Master_hl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值