Почему String неизменяем в Java?


177

В одном из интервью меня спросили, почему String неизменен

Я ответил так:

Когда мы создаем строку в Java, как String s1="hello";тогда, объект будет создан в пуле строк (привет), и s1 будет указывать на привет. Теперь, если мы снова сделаем это, String s2="hello";другой объект не будет создан, но s2 будет указывать, hello потому что JVM сначала проверит если тот же объект присутствует в пуле строк или нет. Если его нет, то создается только новый, иначе нет.

Теперь , если предположим , что Java позволяет строку Мутабельном тогда , если мы изменим s1 на hello worldто s2 значение также будет hello worldтак String Java , неизменна.

Любое тело, пожалуйста, скажите мне, если мой ответ правильный или неправильный ?


46
Почему всегда сложно ответить. Вероятно, самый правильный ответ: потому что дизайнеры языка решили, что это хорошая идея.
Кеппил

1
см. также этот ответ

3
Ваш ответ не в этом суть. C ++ std::stringявляется изменяемым, но у них также есть пул строк (ну, точнее, пул символьных массивов).
Сиюань Рен

1
@ Rocking Если честно, то, правильно это или нет, зависит от того, как они это читают. Дело в том, что у Java может быть пул строк, потому что строки неизменны. Если бы они решили сделать строки изменяемыми, они бы не использовали пул строк; поэтому может быть неточно сказать «пул строк, следовательно, неизменяемые строки»; это скорее наоборот. В причинах выбора неизменных строк описаны ниже, а строка пул представляет собой рабочая стратегию из - за этого. Тем не менее, ваш ответ не является неправильным , он просто не кажется полным. Вам просто нужно подождать и посмотреть, что они говорят.
Джейсон С

34
Я просто не могу понять, почему этот вопрос был закрыт. Предполагаемый связанный ответ даже не касается Java и не затрагивает основной предмет этого вопроса, а именно «почему». Для меня это один из тех случаев, когда безответственное сообщество действует по вопросу, о котором ничего не знает. Я назначил его, чтобы открыть снова.
Эдвин Далорсо

Ответы:


163

String является неизменным по нескольким причинам, вот резюме:

  • Безопасность : параметры обычно представлены Stringв виде сетевых подключений, URL-адресов подключений к базе данных, имен пользователей / паролей и т. Д. Если бы они были изменяемыми, эти параметры можно было бы легко изменить.
  • Синхронизация и параллелизм: автоматическая неизменность String делает их потокобезопасными, тем самым решая проблемы синхронизации.
  • Кэширование : когда компилятор оптимизирует ваши объекты String, он видит, что если два объекта имеют одинаковое значение (a = "test" и b = "test"), и, таким образом, вам нужен только один строковый объект (для a и b, эти два будут указать на тот же объект).
  • Загрузка класса : Stringиспользуется в качестве аргументов для загрузки класса. Если он изменчив, это может привести к загрузке неправильного класса (поскольку изменяемые объекты меняют свое состояние).

При этом неизменность Stringтолько означает, что вы не можете изменить его, используя его публичный API. Вы можете фактически обойти нормальный API, используя отражение. Смотрите ответ здесь .

В вашем примере, если он Stringбыл изменчив, рассмотрите следующий пример:

  String a="stack";
  System.out.println(a);//prints stack
  a.setValue("overflow");
  System.out.println(a);//if mutable it would print overflow

14
Как это может повлиять на безопасность?
Archit Maheshwari

2
Может ли кто-нибудь объяснить загрузку класса примером, если это возможно?
Вирадж

6
Что касается безопасности, если меня интересует изменение параметров соединения, это просто во время выполнения (с отладчиком и т. Д.). Что касается загрузки классов, если Stringона изменчива, то загрузчик классов будет принимать переданную строку, делать копию и не изменять ее. Когда вы думаете о проблеме с изменяемыми java.lang.Strings, подумайте о том, как C ++ решает эту проблему (поскольку она имеет изменяемые std::strings.
Ограниченное искупление

Что касается безопасности, как можно изменить изменяемую строку при запуске программы?
MasterJoe2

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

45

Разработчики Java решают, что строки являются неизменяемыми из-за следующего аспектного дизайна, эффективности и безопасности .

Строки проектирования создаются в специальной области памяти в куче Java, известной как «Внутренний пул строк». При создании новой строки (не в случае использования конструктора String () или любых других функций String, которые внутренне используют конструктор String () для создания нового объекта String, конструктор String () всегда создает новую строковую константу в пуле, если только мы не вызывая переменную метода intern (), он ищет пул, чтобы проверить, существует ли он уже. Если он существует, вернуть ссылку на существующий объект String. Если строка не является неизменной, изменение строки с одной ссылкой приведет к неправильному значению для других ссылок.

Согласно этой статье на DZone:

Строка безопасности широко используется в качестве параметра для многих классов Java, например, для сетевого подключения, открытия файлов и т. Д. Если бы строка не была неизменной, соединение или файл могли бы быть изменены и привести к серьезной угрозе безопасности. Изменяемые строки могут также вызвать проблемы с безопасностью в Reflection, поскольку параметры являются строками.

Эффективность Хеш-код строки часто используется в Java. Например, в HashMap. Наличие неизменяемости гарантирует, что хеш-код всегда будет одинаковым, поэтому его можно будет кэшировать, не беспокоясь об изменениях. Это означает, что нет необходимости вычислять хеш-код при каждом его использовании.


7
Ваше понимание пула строк неверно. Строковые константы создаются во внутреннем пуле, но вполне возможно иметь более одного строкового объекта с одним и тем же текстом. Я согласен, что неизменяемость строк позволяет создавать пулы, но вы не указали такого большого количества пулов.
Джон Скит

@JonSkeet Вы правы. String s1 = new String ("test"); оператор создает новую строковую константу в пуле интернов, если мы не вызываем метод intern (). Спасибо за углубление моих знаний о струнном пуле.
Алекс Мэтью

2
Это больше, чем просто использование строкового конструктора - почти все, что создает новую строку, например substring, split, concat и т. Д., Создаст новые строки. Константы времени компиляции - это особый случай, а не норма ...
Джон Скит

@JonSkeet substring (), concat (), replace () и т. Д. Внутренне используют конструктор String для создания нового строкового объекта. Спасибо за улучшение моего ответа.
Алекс Мэтью

2
@JonSkeet - Все эти ответы говорят о том, что неизменность повышает «безопасность», но не объясняют, как. Все они ссылаются на расплывчатую статью о dzone, которая тоже не помогает. Ответы / ссылки не объясняют, как изменяемая строка может быть изменена во время выполнения кода. Не могли бы вы объяснить?
MasterJoe2

25

Мы не можем быть уверены в том, о чем на самом деле думали Java-дизайнеры при проектировании, Stringно мы можем только заключить эти причины, основываясь на преимуществах, которые мы получаем от неизменяемости строк, некоторые из которых

1. Наличие пула констант

Как обсуждалось в статье « Почему хранилище строк в статье String Constant Pool» , каждое приложение создает слишком много строковых объектов, чтобы спасти JVM от первоначального создания большого количества строковых объектов, а затем сбора мусора. JVM хранит все строковые объекты в отдельной области памяти, которая называется пулом констант String, и повторно использует объекты из этого кэшированного пула.

Всякий раз, когда мы создаем строковый литерал, JVM сначала видит, присутствует ли этот литерал в постоянном пуле или нет, и если он там есть, новая ссылка начнет указывать на тот же объект в SCP.

String a = "Naresh";
String b = "Naresh";
String c = "Naresh";

В приведенном выше примере строки объекта со значением Nareshполучит созданный в SCP только один раз , и все ссылки a, b, cбудет указывать на тот же объект , но что , если мы попытаемся внести изменения в aнапример a.replace("a", "").

В идеале, aдолжно иметь значение , Nreshно b, cдолжно оставаться неизменным , поскольку в качестве конечного пользователя мы делаем изменения в aтолько. И мы знаем a, b, cвсе они указывают на тот же объект , так что если мы делаем изменения a, другие должны также отражать изменения.

Но неизменность строки спасает нас от этого сценария и из-за неизменности строкового объекта строковый объект Nareshникогда не изменится. Поэтому, когда мы вносим какое-либо изменение aвместо строкового объекта, NareshJVM создает новый объект, назначает его aи затем вносит изменения в этот объект.

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

И именно поэтому он обрабатывается JVM очень специально и получил специальную область памяти.

2. Поток безопасности

Объект называется поточно-ориентированным, когда на нем работают несколько потоков, но ни один из них не способен испортить его состояние, и объект в любой момент времени поддерживает одно и то же состояние для каждого потока.

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

Таким образом, из-за своей неизменной природы строковый объект может совместно использоваться несколькими потоками, и даже если им манипулируют многие потоки, он не изменит своего значения.

3. Безопасность

В каждом приложении нам нужно передать несколько секретов, например, имя пользователя \ пароли, URL-адреса подключения и, в общем, всю эту информацию передают как строковый объект.

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

4. Класс загрузки

Как обсуждалось в разделе Создание объектов с помощью Reflection в Java с примером , мы можем использовать Class.forName("class_name")метод для загрузки класса в память, который снова вызывает для этого другие методы. И даже JVM использует эти методы для загрузки классов.

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

Предположим, что если String не был бы неизменным, и мы пытаемся загрузить то, java.lang.Objectчто изменяется org.theft.OurObjectмежду ними, и теперь все наши объекты имеют поведение, которое кто-то может использовать для нежелательных вещей.

5. Кэширование HashCode

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

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

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

Узнайте больше о том, почему String является неизменным и окончательным в Java .


1
Относительно безопасности - Как изменяемое значение строки может быть изменено в памяти? Как другой человек может получить доступ к нашим ссылкам на переменные?
MasterJoe2

Дело не в том, как можно получить доступ к ссылкам, а в том, что если у кого-то есть доступ к этим ссылкам? как уже упоминалось, «если бы String не был неизменным по своей природе, это вызвало бы серьезную угрозу безопасности приложения, поскольку эти значения могут быть изменены, а если это разрешено, то они могут быть изменены из-за неправильно написанного кода или любого другого лица». у кого есть доступ к нашим ссылкам на переменные. "
Нареш Джоши

То, как здесь важно. Можно либо получить доступ к ссылкам, либо нет. Если возможно, то можете ли вы назвать 1-2 техники *** (т.е. как), которые можно использовать для этого? Если это невозможно, то пункт о безопасности не применим. *** Пример - Назовите один метод для атаки на БД веб-приложения -> SQL-инъекция. Знаете ли вы какие-либо методы для атаки на ссылки?
MasterJoe2

Как уже упоминалось, «это может произойти из-за неправильно написанного кода или любых изменений, сделанных другим человеком, который имеет доступ к нашим ссылкам на переменные». Например, предположим, что String является изменяемой, и вы пишете какой-либо метод, который использует строку как секрет строки, и снова эта строка передается нескольким другим методам между ними, и один из этих методов не записан вами, и этот метод внес в него некоторые изменения. Теперь строка после вызова всех этих методов возвращает ваш метод, и вы снова используете эту строку, но она была изменена.
Нареш Джоши

2
Пожалуйста, раскрывайте информацию об аффилированных лицах и не используйте сайт для продвижения вашего сайта посредством публикации. Смотрите, как мне написать хороший ответ? ,
Иветт

21

Наиболее важная причина в соответствии с этой статьей на DZone:

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

Безопасность

Строка широко используется в качестве параметра для многих классов Java, например, сетевого подключения, открытия файлов и т. Д. Если бы строка не была неизменной, соединение или файл могли бы быть изменены и привести к серьезной угрозе безопасности. ...

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


@JasonC Я просто хочу знать, является ли мой ответ неправильным или нет. Я уже присутствовал на собеседовании и жду результата. Если ответ сказал, что он правильный, тогда меня выберут
раскачивание

1
Насколько мне известно, ваш ответ правильный, но неизменный означает, что ссылка никогда не изменит место наведения. Все лучше для вашего интервью.
JDGuide

1
Если принять вашу точку № 1, то все объекты должны быть неизменными.
NICOMP

Привет, JDeveloper, я отредактировал твой ответ, чтобы дать правильную ссылку на источник твоего ответа. Не забывайте всегда использовать блочные кавычки для дословных копий контента. Спасибо!
NickL

Статья DZone содержит серьезные ошибки в работе пула Strign. Это только для констант. Поэтому заявленное обоснование недействительно.
Маркиз Лорн

4

Я прочитал этот пост, почему String является неизменным или окончательным в Java, и предположил, что следующие могут быть наиболее важной причиной:

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


1

Ты прав. StringВ Java используется понятие String Poolлитерала. Когда строка создана и если строка уже существует в пуле, вместо создания нового объекта и возврата его ссылки будет возвращена ссылка на существующую строку. Если строка не является неизменной, изменение строки с одной ссылкой приведет к привести к неправильному значению для других ссылок.

Я хотел бы добавить еще одну вещь, поскольку Stringона неизменна, она безопасна для многопоточности, и один экземпляр String может совместно использоваться различными потоками. Это позволяет избежать использования синхронизации для безопасности потоков. Строки неявно thread safe.


0

String class FINALозначает, что вы не можете создать какой-либо класс, чтобы наследовать его, изменить базовую структуру и сделать изменяемым Sting.

Другая переменная экземпляра и предоставляемые методы класса String таковы, что вы не можете изменить Stringобъект после его создания.

Причина, по которой вы добавили, вовсе не делает String неизменной. Это все говорит о том, как String хранится в куче. Также пул строк имеет огромное значение в производительности.


11
Если класс объявлен как final, это означает, что класс не может быть унаследован, но это не означает, что поля экземпляров класса не могут быть изменены, и поэтому класс является неизменным.
Дмитрий Быченко

@Zeeshan: примеры классов, которые вы даете, являются неизменными.
Сиюань Рен

0

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


0

Наиболее важной причиной того, что String стал неизменным в Java, является соображение безопасности . Следующим будет кеширование .

Я считаю, что другие причины, приведенные здесь, такие как эффективность, параллелизм, дизайн и пул строк, вытекают из того факта, что String in сделан неизменным. Например, String Pool мог быть создан, потому что String был неизменным, а не наоборот.

Проверьте стенограмму интервью Гослинга здесь

С стратегической точки зрения они чаще бывают беспроблемными. И обычно есть вещи, которые вы можете сделать с неизменяемыми, которые вы не можете сделать с изменяемыми, например, кешировать результат. Если вы передаете строку в метод открытия файла или если вы передаете строку конструктору для метки в пользовательском интерфейсе, в некоторых API (например, во многих API Windows) вы передаете массив символов. Получатель этого объекта действительно должен скопировать его, потому что они ничего не знают о сроке его хранения. И они не знают, что происходит с объектом, меняется ли он у них под ногами.

В итоге вы почти вынуждены копировать объект, потому что не знаете, сможете ли вы владеть им. И одна из приятных вещей в неизменяемых объектах заключается в том, что ответ таков: «Да, конечно, вы делаете». Потому что вопроса о праве собственности, кто имеет право его менять, не существует.

Одной из причин, по которой Strings стали неизменными, была безопасность. У вас есть метод открытия файла. Вы передаете ей строку. А затем он выполняет все виды проверок подлинности, прежде чем приступить к выполнению вызова ОС. Если вам удастся сделать что-то, что эффективно мутировало String, после проверки безопасности и перед вызовом ОС, тогда вы в boom. Но строки являются неизменяемыми, поэтому атака такого типа не работает. Этот точный пример - то, что действительно требовало, чтобы строки были неизменными


0

В дополнение к отличным ответам я хотел бы добавить несколько моментов. Как и Strings, Array содержит ссылку на начало массива, поэтому, если вы создадите два массива arr1и, arr2и сделали что-то подобное arr2 = arr1, ссылка будет такой arr2же, как, arr1следовательно, изменение значения в одном из них приведет к изменению, например, другого.

public class Main {
    public static void main(String[] args) {
        int[] a = {1, 2, 3, 4};
        int[] b = a;
        a[0] = 8;
        b[1] = 7;
        System.out.println("A: " + a[0] + ", B: " + b[0]);
        System.out.println("A: " + a[1] + ", B: " + b[1]);
        //outputs
        //A: 8, B: 8
        //A: 7, B: 7
    }
}

Мало того, что это вызовет ошибки в коде, он также может (и будет) использоваться злоумышленником. Предположим, если у вас есть система, которая меняет пароль администратора. Пользователь должен сначала ввести, newPasswordа затем, oldPasswordесли он такой oldPasswordже, как adminPassпрограмма, сменить пароль adminPass = newPassword. скажем, новый пароль имеет ту же ссылку, что и пароль администратора, поэтому плохой программист может создать tempпеременную для хранения пароля администратора до того, как пользователь введет данные, если oldPasswordон равен, tempто в противном случае пароль будет изменен.adminPass = temp, Кто-то, зная, что может легко ввести новый пароль и никогда не вводить старый пароль и абракадабру, у него есть доступ администратора. Еще одна вещь , которую я не понимаю , когда изучение Струны почему не JVM создать новую строку для каждого объекта и имеют уникальное место в памяти для него , и вы можете просто сделать это с помощью new String("str");Причину вы не хотите , чтобы всегда использовать newэто потому что это не эффективно с памятью и медленнее в большинстве случаев читать дальше .


0

Если HELLOэто ваша строка, то вы не можете изменить HELLOна HILLO. Это свойство называется свойством неизменности.

Вы можете иметь несколько строковых переменных-указателей для указания HELLO String.

Но если HELLO - char Array, тогда вы можете изменить HELLO на HILLO. Например,

char[] charArr = 'HELLO';
char[1] = 'I'; //you can do this

Ответ:

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


-1

С Securityточки зрения мы можем использовать этот практический пример:

DBCursor makeConnection(String IP,String PORT,String USER,String PASS,String TABLE) {

    // if strings were mutable IP,PORT,USER,PASS can be changed by validate function
    Boolean validated = validate(IP,PORT,USER,PASS);

    // here we are not sure if IP, PORT, USER, PASS changed or not ??
    if (validated) {
         DBConnection conn = doConnection(IP,PORT,USER,PASS);
    }

    // rest of the code goes here ....
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.