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

Эффект изменения строки с использованием отражения

Как мы все знаем, String неизменна в java. однако его можно изменить с помощью отражения, получив поле и установив уровень доступа. (Я знаю, что это неважно, я не собираюсь этого делать, этот вопрос является чисто теоретическим).

мой вопрос:, предполагая, что я знаю, что я делаю (и изменяю все поля по мере необходимости), будет ли программа работать правильно? или jvm делает некоторые оптимизации, которые полагаются на String неизменяемым? я буду терпеть потерю производительности? если да, то какое это предположение? что пойдет не так в программе

p.s. Строка - это всего лишь пример, на самом деле меня интересует общий ответ, помимо примера.

спасибо!

4b9b3361

Ответ 1

Вы обязательно просите о неприятностях, если вы это сделаете. Означает ли это, что вы обязательно увидите ошибки сразу? Нет. Во многих случаях вы можете избежать неприятностей, в зависимости от того, что вы делаете.

Вот несколько случаев, когда это вас укусит:

  • Вы изменяете строку, которая, как оказалось, была объявлена ​​как буквальная внутри кода. Например, у вас есть function и где-то он называется как function("Bob"); в этом случае строка "Bob" изменяется во всем приложении (это также относится к строковым константам, объявленным как final).
  • Вы изменяете строку, которая используется в подстрочных операциях или которая является результатом операции подстроки. В Java, взятие подстроки строки фактически использует тот же базовый массив символов, что и исходная строка, что означает, что изменения в исходной строке будут влиять на подстроки (и наоборот).
  • Вы изменяете строку, которая иногда используется как ключ на карте. Он больше не будет сравниваться с его исходным значением, поэтому поиск не будет выполнен.

Я знаю, что этот вопрос касается Java, но я написал сообщение в блоге, показывая, как безумно может работать ваша программа, если вы мутируете строка в .NET. Ситуации действительно очень похожи.

Ответ 2

После компиляции некоторые строки могут ссылаться на один экземпляр, поэтому вы будете редактировать больше, чем хотите, и никогда не знаете, что еще вы редактируете.

public static void main(String args[]) throws Exception {
    String s1 = "Hello"; // I want to edit it
    String s2 = "Hello"; // It may be anywhere and must not be edited
    Field f = String.class.getDeclaredField("value");
    f.setAccessible(true);
    f.set(s1, "Go to hell".toCharArray());
    System.out.println(s2);
}

Вывод:

Go to hell

Ответ 3

То, что мне подсказывает, - это интерпретация строк - литералы, все в постоянном пуле и все, что вручную intern() ed указывает на один и тот же строковый объект. Если вы начнете возиться с содержимым интернированного строкового литерала, вы можете увидеть точно такие же изменения во всех других литералах, используя один и тот же базовый объект.

Я не уверен, действительно ли это произошло с тех пор, как я никогда не пробовал (теоретически это будет, я не знаю, если что-то случится под сценой, чтобы остановить его, но я сомневаюсь), но это похоже на то, что может вызвать потенциальные проблемы. Конечно, это может также вызвать проблемы на уровне Java, просто передав несколько ссылок на одну и ту же строку, а затем используя атаку отражения, чтобы изменить объект из одной из ссылок. Большинство людей (включая меня!) Не будут явно защищать от такого рода вещи в коде, поэтому использование этой атаки с любым кодом, который не является вашим собственным, или ваш собственный код, если вы не защитили его, может вызвать все виды странных, ужасных ошибок.

Это интересная область теоретически, но чем больше вы копаете, тем больше вы видите, почему что-то в этом духе - плохая идея!

Выступая за пределами строки, нет улучшений производительности, о которых я знаю, поскольку объект неизменен (действительно, я не думаю, что JVM может даже сказать в данный момент, является ли объект неизменным, отвлечься от отражений). Это может бросить вещи например checker-framework, хотя или что-либо, что пытается статически анализировать код, чтобы гарантировать его неизменность.

Ответ 4

Я уверен, что сам JVM не делает никаких предположений о неизменности строк, поскольку "неизменность" в Java не является конструкцией на уровне языка; это черта, подразумеваемая реализацией класса, но не может, как вы заметили, быть фактически гарантированной при наличии отражения. Таким образом, это также не должно иметь отношения к производительности.

Тем не менее, в значительной степени весь существующий Java-код (включая реализацию стандартного API) полагается на неизменность строк, и если вы нарушаете это ожидание, вы увидите всевозможные ошибки.

Ответ 5

Частными полями в классе String являются char [], смещение и длина. Изменение любого из них не должно оказывать никакого неблагоприятного воздействия на любой другой объект. Но если вы можете каким-то образом изменить содержимое char [], то, возможно, вы увидите некоторые неожиданные побочные эффекты.

Ответ 6

public static void main(String args[]){
    String a = "test213";
    String s = new String("test213");
    try {
        System.out.println(s);
        System.out.println(a);
        char[] value = (char[])getFieldValue(s, "value");
        value[1] = 'a';
        System.out.println(s);
        System.out.println(a);

    } catch (Exception e) {
        e.printStackTrace();
    }
}

static Object getFieldValue(String s,String fieldName) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
    Object chars = null;
    Field innerCharArray = String.class.getDeclaredField(fieldName);
    innerCharArray.setAccessible(true);
    chars = innerCharArray.get(s);
    return chars;
}

Изменение значения S изменит литерал, как указано во всех ответах.

Ответ 7

Чтобы продемонстрировать, как это может испортить программу:

System.out.print("Initial: "); System.out.println(addr);
editIntStr("ADDR_PLACEH", "192.168.1.1");
System.out.print("From var: "); System.out.println(addr);//
System.out.print("Hardcoded: "); System.out.println("ADDR_PLACEH");
System.out.print("Substring: "); System.out.println("ADDR_PLACE" + "H".substring(0));
System.out.print("Equals test: "); System.out.println("ADDR_PLACEH".equals("192.168.1.1"));
System.out.print("Equals test with substring: ");  System.out.println(("ADDR_PLACE" + "H".substring(0)).equals("192.168.1.1"));

Вывод:

Initial: ADDR_PLACEH
From var: 192.168.1.1
Hardcoded: 192.168.1.1
Substring: ADDR_PLACEH
Equals test: true
Equals test with substring: false

Результат первого теста Equals является странным, не так ли? Вы не можете ожидать, что ваши коллеги-программисты поймут, почему Java думает, что они равны...
Полный тестовый код: http://pastebin.com/vbstfWX1