Java Puzzle with reflection and String

2022-09-04 08:17:56

此源输出 这是如何发生的?G'Day Mate.

public static void main(String args[]) {
    System.out.println("Hello World");
}

static {
    try {
        Field value = String.class.getDeclaredField("value");
        value.setAccessible(true);
        value.set("Hello World", value.get("G'Day Mate."));
    } catch (Exception e) {
        throw new AssertionError(e);
    }
}

如果我们将主要函数更改为:"Hello World"new String("Hello World")

System.out.println(new String("Hello World"));

它输出 .Hello world

到底发生了什么?


答案 1

这个源代码开辟了一些有趣的java技术。让我们逐一检查。

首先,我们需要了解代码的流程。代码的哪一部分将首先执行?

静态初始化块。为什么?让我们参考 Java 语言规范 (12.4):

类的初始化包括执行其静态初始值设定项和类中声明的静态字段(类变量)的初始值设定项。

它何时发生?再次来自 JLS (12.4.1)

T 是一个类,调用由 T 声明的静态方法。

因此,我们可以得出结论,静态初始化器将首先在main方法之前执行。

现在,这两条线使用反射:

Field value = String.class.getDeclaredField("value");
value.setAccessible(true);

为了简单起见,我们可以将拳头线分成两行:

Class<String> c = String.class;
Field value = c.getDeclaredField("value");

第一行检索反射的类对象,第二行检索表示类的字段的 a。FieldvalueString

value.setAccessible(true)指示反射的类对象在使用时应禁止 Java 语言访问检查。(参考资料)。

下一行是

value.set("Hello World", value.get("G'Day Mate."));

如果我们深入研究 .set() 文档,我们可以看到我们正在调用 的版本。 返回 的字段的值,该值实际上是一个 。并且通过它的调用,将对象的值字段的值替换为对象的值字段。set(Object aObject,Object value)setvalue.get("G'Day Mate.")"G'Day Mate."valuechar[]set"Hello World""G'Day Mate."

块的代码已解释。static

让我们深入了解主要功能。这很简单。它应该输出 。但它正在输出 。为什么?因为我们在初始值设定项中创建的 String 对象与我们在 main 函数中使用的对象相同。再次咨询JLS将阐明这一点Hello, worldG'Day MateHello, worldstaticHello, world

此外,字符串文本始终引用类 String 的同一实例。这是因为字符串文本 (或者更一般地说,作为常量表达式的值的字符串 (§15.28) ) 被“暂存”,以便使用 String.intern 方法共享唯一实例。

这个答案可以帮助你更简洁地理解事实。

因此,它显示不同的值,因为我们已经将对象的值更改为 。Hello,worldG'Day, Mate

但是,如果您在main函数中使用,它将直接创建一个新的实例,而不是签入其池。因此,main 函数将不同于静态初始值设定项,我们已更改其值。new String("Hello world")StringHello worldHello world


答案 2

As 在堆中创建新对象,而不是使用以前在字符串常量池中创建的对象。new String("Hello World")"Hello World"

所以,如果你写

System.out.print(new String("Hello World").intern());

它将输出显示为 .因为方法从 返回字符串实例的引用 ID。G'Day, Mateintern()String Constant Pool