通过反射改变私人最终领域

Ale*_*ndr 50 java reflection final

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));
Run Code Online (Sandbox Code Playgroud)

输出:

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

为什么它以这种方式工作,你能解释一下吗?第一个印刷品告诉我们私人"s"字段没有像我期望的那样被改变.但是如果我们通过反射获得该字段,则第二个打印显示,它会更新.

Jir*_*era 71

这个答案不仅仅是详尽无遗的主题.

JLS 17.5.3最终字段的后续修改

即使这样,也有许多并发症.如果在字段声明中将final字段初始化为编译时常量,则可能无法观察到对final字段的更改,因为在编译时将该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;
    }
  }

}
Run Code Online (Sandbox Code Playgroud)

输出如下:

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

希望这个对你有帮助.

  • @Stephen C:实际上这种行为与*JIT*编译无关.它已经是字节码编译器用`compile's ="+ s;`替换`s`和编译时常量.这可以通过创建两个类"A"和"B"来证明,以便"A"指的是在"B"中定义的常量.现在,改变"B"中的常量并仅重新编译"B"将使"A"中的旧常量保持不变!偷偷摸摸,但确实如此. (5认同)
  • 我认为您不必恢复字段可访问性,因为它实际上是某些 Java 内部“Field”实例的副本。这样的副本仅供您使用。因此,如果您稍后不需要在代码中读取原始的“accessible”标志状态,则不必保留然后恢复其状态。 (2认同)

Joo*_*kka 15

这个

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

实际编译如下:

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

也就是说,编译时常量会内联.看到这个问题.避免内联的最简单方法是声明String如下:

private final String s = "I’m totally safe".intern();
Run Code Online (Sandbox Code Playgroud)

对于其他类型,一个简单的方法调用可以解决这个问题:

private final int integerConstant = identity(42);
private static int identity(int number) {
    return number;
}
Run Code Online (Sandbox Code Playgroud)

  • 实际上,而是使用`toString`,因为它避免了在字符串池中查找. (3认同)

Ber*_*t F 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
Run Code Online (Sandbox Code Playgroud)

注意,在toString()方法中,地址0 [ 0 ldc <String "s = I’m totally safe"> [23]] 使用的常量显示编译器已经预先将字符串文字"s = "和私有最终字段" I’m totally safe"连接在一起并存储它."s = I’m totally safe"无论实例变量如何s变化,toString()方法都将始终返回.