String является неизменным *, но это только означает, что вы не можете изменить его с помощью его открытого API.
То, что вы делаете здесь, - это обход нормального API с использованием рефлексии. Таким же образом вы можете изменить значения перечислений, изменить таблицу поиска, используемую в автобоксах Integer и т. Д.
Теперь причина s1и s2значение изменения в том, что они оба ссылаются на одну и ту же интернированную строку. Компилятор делает это (как указано в других ответах).
Причина s3вовсе не было на самом деле немного удивительно для меня, как я думал , что это будет делить valueмассив ( это было в предыдущей версии Java до Java 7u6). Однако, глядя на исходный код String, мы видим, что valueмассив символов для подстроки на самом деле копируется (используя Arrays.copyOfRange(..)). Вот почему он остается неизменным.
Вы можете установить SecurityManager, чтобы избежать вредоносного кода, чтобы делать такие вещи. Но имейте в виду, что некоторые библиотеки зависят от использования таких трюков отражения (обычно это инструменты ORM, библиотеки AOP и т. Д.).
*) Я изначально писал, что Strings на самом деле не являются неизменяемыми, просто «эффективные неизменяемыми». Это может ввести в заблуждение в текущей реализации String, где valueмассив действительно отмечен private final. Тем не менее, все же стоит отметить, что в Java нет способа объявить массив неизменным, поэтому следует позаботиться о том, чтобы не раскрывать его вне своего класса, даже с соответствующими модификаторами доступа.
Поскольку эта тема кажется невероятно популярной, вот некоторые из них, предлагаемые для дальнейшего чтения: доклад Хайнца Кабуца о безумии отражения от JavaZone 2009, который охватывает многие проблемы в OP, а также другие размышления ... ну ... безумие.
Это объясняет, почему это иногда полезно. И почему, в большинстве случаев, вам следует избегать этого. :-)