Подтвердить что ты не робот

Как этот код Java Reflection улавливает строку в System.out.println?

Можете ли вы объяснить мне, что произойдет с выполнением этого кода? Я знаю, что он печатает "G'Day Mate.", Но как Reflection улавливает 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);
        }
    }
4b9b3361

Ответ 1

Отражение не "ломает" System.out. Конечно, вы выбрали и самый сложный пример - String, и это потому, что класс java 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 и функцию отражения.

Ответ 2

Отличный вопрос... я понял, что

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 Pool строка была изменена, но клавиша остается Hello World.

Интересно, если в заявлении

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

более поздняя длина строки меньше... она будет бросать ArrayIndexOutOfBoundException всякий раз, когда обращается к String Hello World.

Надеюсь, это поможет!!!

Ответ 3

javadoc для java.lang.reflect.Field.set(объект, значение)

Устанавливает поле, представленное этим объектом Field, в указанном аргументе объекта указанному новому значению. Новое значение автоматически распаковывается, если базовое поле имеет примитивный тип.

Операция выполняется следующим образом:

Если базовое поле является статическим, аргумент obj игнорируется; он может быть нулевым.

В противном случае базовое поле будет полем экземпляра. Если указанный аргумент объекта равен нулю, метод генерирует исключение NullPointerException. Если указанный аргумент объекта не является экземпляром класса или интерфейса, объявляющим базовое поле, метод генерирует исключение IllegalArgumentException.

Если этот объект Field обеспечивает контроль доступа к языку Java, а базовое поле недоступно, метод генерирует исключение IllegalAccessException.

Если базовое поле является окончательным, метод генерирует исключение IllegalAccessException, если для этого объекта Field не было выполнено setAccessible (true), а поле нестатически. Установка конечного поля таким образом имеет смысл только во время десериализации или восстановления экземпляров классов с пустым финальным полем, прежде чем они станут доступными для доступа другими частями программы. Использование в любом другом контексте может иметь непредсказуемые последствия, в том числе случаи, когда другие части программы продолжают использовать исходное значение этого поля.

Если базовое поле имеет примитивный тип, конвертируемое преобразование пытается преобразовать новое значение в значение примитивного типа. Если эта попытка не удалась, метод выдает исключение IllegalArgumentException.

Если после разворачивания новое значение не может быть преобразовано в тип базового поля с помощью идентификатора или расширения преобразования, метод генерирует исключение IllegalArgumentException.

Если базовое поле является статическим, класс, объявивший это поле, инициализируется, если он еще не был инициализирован.

Поле установлено как возможно развернутое и расширенное новое значение.

Если поле скрыто в типе obj, значение поля устанавливается в соответствии с предыдущими правилами. Параметры: obj - объект, поле которого необходимо изменить value - новое значение для изменяемого поля obj

Ответ 4

Этот исходный код открывает некоторые интересные методы 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.

Итак, мы можем прийти к выводу, что статический initiazlier будет выполняться первым перед основным методом.

Теперь эти две строки используют отражение:

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

Для простоты мы можем разбить первую строку на две строки:

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

Первая строка возвращает Reflected Class Object, а вторая строка возвращает Field, которая представляет поле value класс String.

value.setAccessible(true) указывает, что объект отраженного класса должен подавлять проверку доступа к языку Java, когда он используется. (Ссылка).

Неактивная строка под вопросом

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

Если мы погрузимся в . set() documenation, мы увидим, что мы вызываем set(Object aObject,Object value) версию set. value.get("G'Day Mate.") возвращает значение поля "G'Day Mate." value, которое на самом деле является char[]. И при вызове set он заменяет значение поля значения объекта "Hello World" значением поля значения "G'Day Mate.".

Объясняется блок-код static.

Позволяет погрузиться в основной функционал. Это довольно просто. Он должен выводить Hello, world. Но он выводит G'Day Mate. Зачем? Поскольку объект Hello, world String, который мы создали в инициализаторе static, тот же, что и объект Hello, world, который мы используем в основной функции. Консультирование с JLS снова проливает свет на него

Кроме того, строковый литерал всегда ссылается на тот же экземпляр класса String. Это связано с тем, что строковые литералы, или, в более общем смысле, строки, которые являются значениями константных выражений (§15.28), "интернированы", чтобы обмениваться уникальными экземплярами, используя метод String.intern.

Этот ответ может помочь вам понять факт более кратко.

Таким образом, он показывает другое значение, поскольку мы уже изменили значение объекта Hello,world на G'Day, Mate.

Но если вы используете new String("Hello world") в основной функции, он будет непосредственно создавать новый экземпляр String, а не проверять его пул. Таким образом, Hello world основной функции будет отличаться от Hello world статического инициализатора, из которого мы изменили значение.

Ответ 5

Из документов Oracle (Java):

Класс может иметь любое количество статических блоков инициализации, и они могут появляться в любом месте тела класса. Система времени выполнения гарантирует, что статические блоки инициализации вызываются в том порядке, в котором они отображаются в исходном коде.

Вот ссылка