12

你能向我解释一下执行这段代码会发生什么吗?我知道它会打印出“G'Day Mate。”,但是反射如何捕获 System.out.println?在堆栈/堆级别会发生什么?非常感谢。

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

5 回答 5

8

反射不会“捕捉” System.out。当然,您已经选择了最难的示例 - String,这是因为 java String 类是一个非常“有趣”的类,其中每个 String 都不是对象,而是在字符串池中产生并且本身是不可变的。

您的代码所做的是在 java String 类中它静态地(这意味着在执行时间之前)将字符串“Hello World”的值设置为“G`Day Mate。”。这意味着每当您使用字符串“Hello World”时,它都会更改为“G`Day Mate.”。例子:

String h ="Hello World";
System.out.println(h);
>>>G`Day Mate.

希望这个例子能有所帮助。有趣的评论,代码:

public static void main(String[] args){
        String h = "Hello";
        System.out.println(h);
        System.out.println("Hello");
    }
     static {
            try {
               Field value = String.class.getDeclaredField("value");
               value.setAccessible(true);
               value.set("Hello", value.get("G'Day Mate."));
            } catch (Exception e) {
              throw new AssertionError(e);
            }
        }

产生输出:

>>>G`Day
>>>G`Day

这意味着在映射中空白会产生一些差异,但我不知道这如何影响 String 对象和反射的功能。

于 2012-07-28T12:28:08.780 回答
1

很好的问题......我能理解的是

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

替换字符串内存池中的值并保持引用相同。意味着在这个程序Hello World中提到的任何地方都会打印G'Day Mate.

这类似于在C 语言中使用指针修改变量内容的情况

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);
 }
}
public static void main(String[] args){
    System.out.println("Hello World");
    System.out.println("Hell World");
    System.out.println("Hello orld");
    System.out.println("Hello World");
    String s = "Hello World";
    System.out.println(s);
}

印刷

G'Day Mate.
Hell World 
Hello orld
G'Day Mate.
G'Day Mate.

所以在String PoolString 已被修改但 key 仍然存在Hello World

有趣的是,如果在声明中

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

后面的 String 的长度更小......ArrayIndexOutOfBoundException每当Hello World访问 String 时它都会抛出。

希望这可以帮助!!!

于 2012-07-28T13:29:44.417 回答
0

java.lang.reflect.Field.set(object, value) 的 javadoc

将指定对象参数上的此 Field 对象表示的字段设置为指定的新值。如果基础字段具有原始类型,则新值会自动展开。

操作如下:

如果底层字段是静态的,则忽略 obj 参数;它可能为空。

否则,基础字段是实例字段。如果指定的对象参数为 null,则该方法将引发 NullPointerException。如果指定的对象参数不是声明基础字段的类或接口的实例,则该方法将引发 IllegalArgumentException。

如果此 Field 对象正在强制执行 Java 语言访问控制,并且基础字段不可访问,则该方法将引发 IllegalAccessException。

如果基础字段是最终字段,则该方法将引发 IllegalAccessException,除非此 Field 对象的 setAccessible(true) 已成功并且该字段是非静态的。以这种方式设置最终字段仅在反序列化或重建具有空白最终字段的类实例期间才有意义,然后它们才可供程序的其他部分访问。在任何其他上下文中使用可能会产生不可预知的影响,包括程序的其他部分继续使用该字段的原始值的情况。

如果基础字段是原始类型,则尝试展开转换以将新值转换为原始类型的值。如果此尝试失败,该方法将引发 IllegalArgumentException。

如果在可能的展开之后,新值无法通过标识或扩展转换转换为基础字段的类型,则该方法将引发 IllegalArgumentException。

如果基础字段是静态的,则声明该字段的类在尚未初始化的情况下被初始化。

该字段设置为可能展开和扩展的新值。

如果字段隐藏在 obj 的类型中,则按照前面的规则设置字段的值。参数: obj - 字段应该被修改的对象 value - 被修改的 obj 字段的新值

于 2012-07-28T12:31:12.040 回答
0

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

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

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

Initialization of a class consists of executing its static initializers and the initializers for static fields (class variables) declared in the class.

什么时候发生?再次来自JLS (12.4.1)

T is a class and a static method declared by T is invoked.

所以我们可以得出结论,static initiazlier 会在 main 方法之前先执行。

现在,这两行正在使用反射:

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

为简单起见,我们可以将第一行分成两行:

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

第一行是检索反射类对象,第二行是检索Field表示类value字段的a String

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

有问题的下一行是

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

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

解释了static块的代码。

让我们深入了解主要功能。这很简单。它应该输出Hello, world. 但它正在输出G'Day Mate。为什么?因为Hello, world我们在初始化器中创建的 String 对象与我们在 main 函数中使用的对象static相同。Hello, world再次与JLS 协商将阐明这一点

此外,字符串字面量总是引用 String 类的同一个实例。这是因为字符串字面量 - 或者更一般地说,作为常量表达式值的字符串(第 15.28 节) - 是“内部的”,以便使用 String.intern 方法共享唯一实例。

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

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

但是,如果您new String("Hello world")在 main 函数中使用,它将直接创建一个新实例,String而不是检入其池中。所以Hello worldmain 函数Hello world与我们改变了值的静态初始化器不同。

于 2013-11-17T21:37:24.597 回答
-4

来自 Oracle (Java) 文档:

一个类可以有任意数量的静态初始化块,它们可以出现在类主体的任何位置。运行时系统保证静态初始化块按照它们在源代码中出现的顺序被调用。

这是整个链接

于 2012-07-28T12:13:55.917 回答