通过reflection改变私人最终领域

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! 

为什么这样工作,你能解释一下吗? 第一印刷品告诉我们,私人领域没有改变,正如我所料。 但是,如果我们通过反思得到这个领域,第二个印刷品显示,它被更新。

这个答案在这个话题上是非常详尽的。

JLS 17.5.3最终字段的后续修改

即使如此,还有一些并发症。 如果最终字段在字段声明中被初始化为编译时常量,则可能不会观察到对最终字段的更改,因为在编译时将该最终字段的使用replace为编译时常量。

但是,如果你仔细阅读上面的段落,你可以在这里find一个方法(在构造函数中而不是在字段定义中设置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! 

希望这个对你有帮助。

这个

 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(); 

对于其他types来说,一个简单的方法调用就可以做到这一点:

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

下面是WithPrivateFinalField类文件的反编译(为简单起见,我把它放在一个单独的类中):

  WithPrivateFinalField(); 0 aload_0 [this] 1 invokespecial java.lang.Object() [13] 4 aload_0 [this] 5 ldc <String "I'm totally safe"> [8] 7 putfield WithPrivateFinalField.s : java.lang.String [15] 10 return Line numbers: [pc: 0, line: 2] [pc: 4, line: 3] [pc: 10, line: 2] Local variable table: [pc: 0, pc: 11] local: this index: 0 type: WithPrivateFinalField // Method descriptor #22 ()Ljava/lang/String; // Stack: 1, Locals: 1 public java.lang.String toString(); 0 ldc <String "s = I'm totally safe"> [23] 2 areturn Line numbers: [pc: 0, line: 6] Local variable table: [pc: 0, pc: 3] local: this index: 0 type: WithPrivateFinalField 

注意在toString()方法中,在地址0 [ 0 ldc <String "s = I'm totally safe"> [23] ]中使用的常量显示编译器已经连接了string文字"s = "和私有final字段" I'm totally safe"提前一起存储。 无论实例variables如何变化,toString()方法总是会返回"s = I'm totally safe"

作为final ,编译器期望值不会改变,所以它可能直接将string硬编码到toString方法中。