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

发布于 2024-10-08 18:44:48 字数 660 浏览 6 评论 0 原文

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”字段没有改变,正如我所期望的那样。但是,如果我们通过反射获取该字段,则第二个打印显示它已更新。

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

Output:

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

Why does it work by this way, can you please explain? The first print tells us that the private "s" field has not been changed, as I expect. But if we get the field via reflection, the second print shows, it is updated.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

巷雨优美回忆 2024-10-15 18:44:48

这个答案对于这个主题来说非常详尽。

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 static 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!

Hope这有点帮助。

This answer is more than exhaustive on the topic.

JLS 17.5.3 Subsequent Modification of Final Fields

Even then, there are a number of complications. If a final field is
initialized to a compile-time constant in the field declaration,
changes to the final field may not be observed, since uses of that
final field are replaced at compile time with the compile-time
constant.

But, if you read the paragraph above very carefully, you may find a way around here (set the private final field in the constructor instead of in the field definition):

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 static class WithPrivateFinalField {
    private final String s;

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

}

The output is then as follows:

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

Hope this helps a bit.

甜心小果奶 2024-10-15 18:44:48

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;
}

This

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

actually compiles like this:

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

That is, compile-time constants get inlined. See this question. The easiest way to avoid inlining is to declare the String like this:

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

For other types, a trivial method call does the trick:

private final int integerConstant = identity(42);
private static int identity(int number) {
    return number;
}
╭ゆ眷念 2024-10-15 18:44:48

下面是 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 ; [23]] 显示编译器已经提前将字符串文字 "s = " 和私有最终字段 " I'm Excellent safe" 连接在一起,存储它。无论实例变量 s 如何变化,toString() 方法都将始终返回 "s = I'm Excellent safe"

Here's a decompile of WithPrivateFinalField class file (I put it in a separate class for simplicity):

  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

Note in the toString() method, the constant used at address 0 [0 ldc <String "s = I’m totally safe"> [23]] shows the compiler already concatenated the string literal "s = " and the private final field " I’m totally safe" together in advance and stored it. The toString() method will always return "s = I’m totally safe" regardless of how the instance variable s changes.

拒绝两难 2024-10-15 18:44:48

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

Being final, the compiler expected the value not to change, so it probably hardcoded the string directly into your toString method.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文