【问题标题】:Changing private final fields via reflection通过反射更改私有最终字段
【发布时间】:2011-05-29 19:39:11
【问题描述】:
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”字段没有改变,正如我所料。但是,如果我们通过反射获得该字段,则第二个打印显示它已更新。

【问题讨论】:

标签: java reflection final


【解决方案1】:

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 - 这是真正的答案。 OP 的程序导致 JIT 编译器假设 s 不会更改为无效。这不是编译器错误,因为 JLS 明确警告说,如果您在设置 final 变量后对其进行修改,则可能会发生此类“坏事”。
  • @Stephen C:实际上该行为与 JIT 编译无关。它已经是字节码编译器,它用编译时常量替换了return "s = " + s; 中的s。这可以通过创建两个类来证明,AB,以便 A 引用在 B 中定义的常量。现在,更改B 中的常量并仅重新编译B 将保留A 中的旧常量!偷偷摸摸,但真实。
  • @Joonas - 好的......但 JIT 编译器进行这种优化也是合法的。
  • @Stephen C:确实。如果 JIT 编译器而不是字节码编译器这样做,IMO 会更好。使用当前的行为,您只需修改类中的标志常量的 即可破坏其他人的类。
  • 我认为您不必恢复字段可访问性,因为它实际上是一些 Java 内部 Field 实例的副本。这样的副本仅供您使用。因此,如果您以后不需要在代码中读取原始 accessible 标志状态,则不必保留然后恢复其状态。
【解决方案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";
    }  
}

也就是说,编译时常量被内联。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;
}

【讨论】:

  • 我真的很喜欢这种清晰的解释和intern() 的方法,以前不知道...谢谢。
  • 其实还是用toString,因为这样可以避免在字符串池中查找。
【解决方案3】:

这是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 &lt;String "s = I’m totally safe"&gt; [23]]表明编译器已经将字符串文字"s = "和私有final字段" I’m totally safe"预先连接在一起并存储起来。无论实例变量s如何变化,toString() 方法都将始终返回"s = I’m totally safe"

【讨论】:

    【解决方案4】:

    作为final,编译器希望该值不会改变,因此它可能将字符串直接硬编码到您的toString 方法中。

    【讨论】:

      猜你喜欢
      • 2011-03-19
      • 2012-06-26
      • 2012-03-12
      • 2014-10-13
      • 2015-04-12
      • 2015-07-08
      • 2020-08-04
      相关资源
      最近更新 更多