通过反射更改私有最终字段

2022-08-31 20:13:22
class WithPrivateFinalField {
    private final String s = "I’m totally safe";
    public String toString() {
        return "s = " + s;
    }
}
WithPrivateFinalField pf = new WithPrivateFinalField();
System.out.println(pf);
Field f = pf.getClass().getDeclaredField("s");
f.setAccessible(true);
System.out.println("f.get(pf): " + f.get(pf));
f.set(pf, "No, you’re not!");
System.out.println(pf);
System.out.println(f.get(pf));

输出:

s = I’m totally safe
f.get(pf): I’m totally safe
s = I’m totally safe
No, you’re not!

为什么它以这种方式工作,你能解释一下吗?第一个打印件告诉我们,私有的“s”字段没有像我预期的那样被更改。但是,如果我们通过反射获得字段,则第二个打印显示,它将被更新。


答案 1

这个答案在这个话题上是详尽无遗的。

JLS 17.5.3 最终字段的后续修改

即便如此,也存在许多并发症。如果在字段声明中将最终字段初始化为编译时常量,则可能不会观察到对最终字段的更改,因为在编译时,该最终字段的使用将替换为编译时常量。

但是,如果您非常仔细地阅读上面的段落,您可能会在这里找到一种方法(在构造函数中设置字段而不是在字段定义中设置字段):private final

import java.lang.reflect.Field;


public class Test {

  public static void main(String[] args) throws Exception {
    WithPrivateFinalField pf = new WithPrivateFinalField();
    System.out.println(pf);
    Field f = pf.getClass().getDeclaredField("s");
    f.setAccessible(true);
    System.out.println("f.get(pf): " + f.get(pf));
    f.set(pf, "No, you’re not!");
    System.out.println(pf);
    System.out.println("f.get(pf): " + f.get(pf));
  }

  private class WithPrivateFinalField {
    private final String s;

    public WithPrivateFinalField() {
      this.s = "I’m totally safe";
    }
    public String toString() {
      return "s = " + s;
    }
  }

}

然后,输出如下所示:

s = I’m totally safe
f.get(pf): I’m totally safe
s = No, you’re not!
f.get(pf): No, you’re not!

希望这有帮助一点。


答案 2

class WithPrivateFinalField {
    private final String s = "I’m totally safe";
    public String toString() {
        return "s = " + s;
    }  
} 

实际上编译如下:

class WithPrivateFinalField {
    private final String s = "I’m totally safe";
    public String toString() {
        return "s = I’m totally safe";
    }  
}

也就是说,编译时常量将内联。请参阅此问题。避免内联的最简单方法是像这样声明:String

private final String s = "I’m totally safe".intern();

对于其他类型,简单的方法调用可以做到这一点:

private final int integerConstant = identity(42);
private static int identity(int number) {
    return number;
}