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”字段没有像我期望的那样被改变。但是如果我们通过反射获得该字段,则第二个打印显示,它会更新。
答案 0 :(得分:70)
This answer在这个主题上不仅仅是详尽无遗。
JLS 17.5.3最终字段的后续修改
即使这样,也有许多并发症。如果是最终字段 在字段声明中初始化为编译时常量, 由于使用了最终字段,因此可能无法观察到对最终字段的更改 在编译时用编译时替换final字段 恒定。
但是,如果你仔细阅读上面的段落,你可能会在这里找到一种方法(在构造函数中设置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!
希望这有点帮助。
答案 1 :(得分:15)
此
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";
}
}
即编译时常量内联。请参阅this问题。避免内联的最简单方法是声明String
,如下所示:
private final String s = "I’m totally safe".intern();
对于其他类型,一个简单的方法调用就可以了:
private final int integerConstant = identity(42);
private static int identity(int number) {
return number;
}
答案 2 :(得分:6)
这是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]
]使用的常量显示编译器已将字符串文字"s = "
和私有最终字段" I’m totally safe"
连接在一起提前并存储它。无论实例变量"s = I’m totally safe"
如何变化,toString()方法都将始终返回s
。
答案 3 :(得分:1)
作为final
,编译器期望值不会改变,因此它可能会将字符串直接硬编码到您的toString
方法中。