简体   繁体   English

这个Reflection Java代码如何捕获System.out.println中的String?

[英]How this Reflection Java Code catch the String in the System.out.println?

Can you explain to me what happens executing this code? 你能解释一下执行这段代码会发生什么吗? I know that it prints out "G'Day Mate.", but how the Reflection catch the System.out.println? 我知道它打印出“G'Day Mate。”,但是Reflection如何捕获System.out.println? What happens at the Stack/Heap level? 堆栈/堆级别会发生什么? Thank you so much. 非常感谢。

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

The reflection does not "catch" the System.out. 反射不会“捕获”System.out。 Of course you have picked and the most hardest example - String and that is because java String class is a very "interesting" class where each String is not an object but spawned in a pool of Strings and is by itself immutable. 当然你选择了最难的例子 - String,因为java String类是一个非常“有趣”的类,其中每个String不是一个对象,而是在一个字符串池中生成,并且本身是不可变的。

What your code does is that in the java String class it statically(which would mean before execution time) sets the value of the String "Hello World" to "G`Day Mate.". 你的代码所做的是在java String类中它静态地(这意味着在执行时间之前)将字符串“Hello World”的值设置为“G`Day Mate。”。 This means that whenever you use the string "Hello World" it would be changed to "G`Day Mate.". 这意味着无论何时使用字符串“Hello World”,它都将更改为“G`Day Mate”。 Example: 例:

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

Hope the example helps a bit. 希望这个例子有所帮助。 Interesting remark, the code: 有趣的评论,代码:

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

Produces output: 产生输出:

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

Which means that in the mapping the white space makes some difference, but I do not know how that effects the String object and the function of the reflect. 这意味着在映射中白色空间会有所不同,但我不知道这对String对象和反射函数有何影响。

Excellent question... what I could understand was 优秀的问题......我能理解的是

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

replaced the value in string memory pool and kept the reference same. 替换了字符串内存池中的值并保持引用相同。 Mean in this program Hello World mentioned at any place will print G'Day Mate. 在这个程序中意味着在任何地方提到的Hello World都会打印G'Day Mate. .

It's similar to case where in C language you modify the content of a variable using a pointer . 它类似于在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);
}

prints 版画

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

So in String Pool the String has been modified but key remains Hello World . 所以在String Pool中String已被修改,但key仍然是Hello World

Interestingly if in the statement 有趣的是,如果在声明中

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

the later String's length is less... it will throw ArrayIndexOutOfBoundException whenever String Hello World is accessed. 后来的String的长度更小......只要访问String Hello World它就会抛出ArrayIndexOutOfBoundException

Hope this helps!!! 希望这可以帮助!!!

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

Sets the field represented by this Field object on the specified object argument to the specified new value. 将指定对象参数上此Field对象表示的字段设置为指定的新值。 The new value is automatically unwrapped if the underlying field has a primitive type. 如果基础字段具有基本类型,则新值将自动解包。

The operation proceeds as follows: 该操作如下:

If the underlying field is static, the obj argument is ignored; 如果底层字段是静态的,则忽略obj参数; it may be null. 它可能是null。

Otherwise the underlying field is an instance field. 否则,基础字段是实例字段。 If the specified object argument is null, the method throws a NullPointerException. 如果指定的对象参数为null,则该方法抛出NullPointerException。 If the specified object argument is not an instance of the class or interface declaring the underlying field, the method throws an IllegalArgumentException. 如果指定的对象参数不是声明基础字段的类或接口的实例,则该方法将抛出IllegalArgumentException。

If this Field object is enforcing Java language access control, and the underlying field is inaccessible, the method throws an IllegalAccessException. 如果此Field对象强制执行Java语言访问控制,并且基础字段不可访问,则该方法将抛出IllegalAccessException。

If the underlying field is final, the method throws an IllegalAccessException unless setAccessible(true) has succeeded for this Field object and the field is non-static. 如果基础字段是final,则该方法抛出IllegalAccessException,除非此Field对象的setAccessible(true)成功并且该字段是非静态的。 Setting a final field in this way is meaningful only during deserialization or reconstruction of instances of classes with blank final fields, before they are made available for access by other parts of a program. 以这种方式设置最终字段仅在反序列化或重建具有空白最终字段的类的实例期间才有意义,然后才能使程序的其他部分访问它们。 Use in any other context may have unpredictable effects, including cases in which other parts of a program continue to use the original value of this field. 在任何其他上下文中使用可能具有不可预测的影响,包括程序的其他部分继续使用该字段的原始值的情况。

If the underlying field is of a primitive type, an unwrapping conversion is attempted to convert the new value to a value of a primitive type. 如果基础字段是基本类型,则尝试进行解包转换以将新值转换为基元类型的值。 If this attempt fails, the method throws an IllegalArgumentException. 如果此尝试失败,则该方法抛出IllegalArgumentException。

If, after possible unwrapping, the new value cannot be converted to the type of the underlying field by an identity or widening conversion, the method throws an IllegalArgumentException. 如果在可能的解包之后,新值无法通过标识或扩展转换转换为基础字段的类型,则该方法将抛出IllegalArgumentException。

If the underlying field is static, the class that declared the field is initialized if it has not already been initialized. 如果基础字段是静态的,则声明该字段的类如果尚未初始化则初始化。

The field is set to the possibly unwrapped and widened new value. 该字段设置为可能未展开和加宽的新值。

If the field is hidden in the type of obj, the field's value is set according to the preceding rules. 如果该字段隐藏在obj类型中,则根据前面的规则设置字段的值。 Parameters: obj - the object whose field should be modified value - the new value for the field of obj being modified 参数:obj - 应修改其字段的对象值 - 要修改的obj字段的新值

This source code opens up some interesting techniques of java. 这个源代码打开了一些有趣的java技术。 Let's examine one by one. 让我们逐一检查。

At first we need to understand the flow of the code. 首先,我们需要了解代码的流程。 Which part of the code will execute first? 代码的哪一部分将首先执行?

The Static Initialization Block. 静态初始化块。 Why? 为什么? Let's consult with Java Language Specification (12.4) : 让我们参考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.

And when does it occur? 它什么时候发生? Again from JLS (12.4.1) : 再次来自JLS(12.4.1)

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

So we can come to the conclusion that static initiazlier will execute first before the main method. 因此我们可以得出结论,静态initiazlier将在main方法之前首先执行。

Now, these two lines are using reflection: 现在,这两行正在使用反射:

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

We can break the fist line into two lines for simplicity: 为简单起见,我们可以将第一行分成两行:

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

The fist line is retrieving the Reflected Class Object and the second line is retrieving a Field which represents the value field of the String class. 第一行是检索Reflected Class Object ,第二行是检索表示String类的value字段的Field

value.setAccessible(true) indicates that the reflected class object should suppress Java language access checking when it is used.( Reference ). value.setAccessible(true)表示反射的类对象在使用时应禁止Java语言访问检查。( 参考 )。

Nex line under question is Nex系列有问题

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

If we dive into .set() documenation we can see that we are calling the set(Object aObject,Object value) version of set . 如果我们深入.SET()documenation我们可以看到,我们调用了set(Object aObject,Object value)的版本set value.get("G'Day Mate.") is returning "G'Day Mate." value.get("G'Day Mate.")正在回归"G'Day Mate." 's value field's value which is actually a char[] . value字段的值实际上是char[] And with the call of set it replaces the value of "Hello World" object's value field with "G'Day Mate." 并且通过set的调用,它将"Hello World"对象的值字段的值替换为"G'Day Mate." object's value field. 对象的值字段。

The static block's code is explained. 解释static块的代码。

Lets dive into main funciton. 让我们潜入主要功能。 It's pretty simple. 这很简单。 It should output Hello, world . 它应该输出Hello, world But it is outputting G'Day Mate . 但它正在输出G'Day Mate Why? 为什么? Because the Hello, world String object we created in the static initializer is the same as Hello, world object we are using in main function. 因为我们在static初始值设定项中创建的Hello, world String对象与我们在main函数中使用的Hello, world对象相同。 Consulting with JLS again will shed light on it 再次咨询JLS将会对此有所了解

Moreover, a string literal always refers to the same instance of class String. 此外,字符串文字始终引用类String的相同实例。 This is because string literals - or, more generally, strings that are the values of constant expressions (§15.28) - are "interned" so as to share unique instances, using the method String.intern. 这是因为字符串文字 - 或者更常见的是作为常量表达式(第15.28节)的值的字符串 - 被“实例化”以便使用String.intern方法共享唯一实例。

This answer can help you to understand the fact more concisely. 这个答案可以帮助您更简洁地理解事实。

So it is showing different value as we have already changed Hello,world object's value to G'Day, Mate . 所以它显示了不同的值,因为我们已经改变了Hello,world对象的价值是G'Day, Mate

But if you use new String("Hello world") in main function it will directly create a fresh instance of String rather than checking into its pool. 但是如果你在main函数中使用new String("Hello world") ,它将直接创建一个新的String实例,而不是检查它的池。 So Hello world of main function would be differnt than Hello world of static initializer of which we have changed the value. 所以主函数的Hello world将不同于我们已经更改了值的静态初始化器的Hello world

From the Oracle (Java) docs: 来自Oracle(Java)文档:

A class can have any number of static initialization blocks, and they can appear anywhere in the class body. 一个类可以有任意数量的静态初始化块,它们可以出现在类体中的任何位置。 The runtime system guarantees that static initialization blocks are called in the order that they appear in the source code. 运行时系统保证按照它们在源代码中出现的顺序调用静态初始化块。

Here is the entire link 这是整个链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM