Переменные строки в Java

Как почти все знают, что строки в Java неизменяемы. Недавно я обнаружил что-то, что может показаться, что это не всегда так. Попробуйте этот код:

System.out.println("-------- BEFORE MODIFICATIONS --------");
String beforeTest = new String("Original");
System.out.println(beforeTest);
java.lang.reflect.Field valueField = String.class.getDeclaredField("value");
valueField.setAccessible(true);
valueField.set("Original", "Modified".toCharArray());
System.out.println("-------- AFTER MODIFICATIONS --------");
System.out.println(beforeTest);
System.out.println("Original");
String test = new String("Original");
System.out.println(test);
String test2 = new String("Original 2");
System.out.println(test2);

вывод будет:

-------- BEFORE MODIFICATIONS --------
Original
-------- AFTER MODIFICATIONS --------
Original
Modified
Modified
Original 2

Как этот трюк работает? Как JVM знает, какие объекты должны быть изменены, а какие нет? Какой механизм находится под капотом этого трюка? Почему уже созданная строка beforeTest не была изменена? Этот трюк действительно умаляет принцип strings are immutable?

Ответ 1

Строковые литералы интернированы в пул. Это означает, что когда вы пишете

String s1 = "Foo";
String s2 = "Foo";
String s3 = new String("Foo");

s1 и s2 относятся к одному и тому же объекту String, а s3 относится к другому, поддерживаемому другим массивом char.

В вашем коде вы нарушаете инварианты String путем изменения частного массива char, содержащего символы экземпляра "Original" String. Но поскольку beforeTest ссылается на другой экземпляр String, он не изменяется.

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