Перейти к основному содержимому

Почему строка неизменяема в Java?

· 5 мин. чтения

1. Введение

В Java строки неизменяемы. Очевидный вопрос, который довольно часто встречается в интервью: «Почему строки спроектированы как неизменяемые в Java?»

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

Я бы использовал неизменяемый всякий раз, когда я могу.

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

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

2. Что такое неизменяемый объект?

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

У нас есть отдельная статья, в которой подробно рассматриваются неизменяемые объекты. Для получения дополнительной информации прочитайте статью Неизменяемые объекты в Java .

3. Почему строка неизменяема в Java?

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

Давайте обсудим, как эти вещи работают.

3.1. Знакомство с пулом строк

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

Java String Pool — это специальная область памяти, в которой JVM хранит строки . Поскольку строки в Java являются неизменяемыми, JVM оптимизирует объем памяти, выделяемой для них, сохраняя только одну копию каждой литеральной строки в пуле. Этот процесс называется стажировкой:

String s1 = "Hello World";
String s2 = "Hello World";

assertThat(s1 == s2).isTrue();

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

./ace3d8d462adef01d7f0205915c0d86f.jpg

У нас есть отдельная статья, посвященная Java String Pool. Для получения дополнительной информации перейдите к этой статье .

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

Строка широко используется в приложениях Java для хранения конфиденциальной информации, такой как имена пользователей, пароли, URL-адреса подключения, сетевые подключения и т. д. Она также широко используется загрузчиками классов JVM при загрузке классов.

Следовательно, защита класса String имеет решающее значение для безопасности всего приложения в целом. Например, рассмотрим этот простой фрагмент кода:

void criticalMethod(String userName) {
// perform security checks
if (!isAlphaNumeric(userName)) {
throw new SecurityException();
}

// do some secondary tasks
initializeDatabase();

// critical task
connection.executeUpdate("UPDATE Customers SET Status = 'Active' " +
" WHERE UserName = '" + userName + "'");
}

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

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

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

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

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

3.3. Синхронизация

Неизменяемость автоматически делает поток String безопасным, поскольку они не будут изменены при доступе из нескольких потоков.

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

3.4. Кэширование хэш-кода

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

Неизменность гарантирует строкам , что их значение не изменится. Таким образом , метод hashCode() переопределяется в классе String для облегчения кэширования, так что хэш вычисляется и кэшируется во время первого вызова hashCode() , и с тех пор возвращается одно и то же значение.

Это, в свою очередь, повышает производительность коллекций, использующих хэш-реализации при работе с объектами String .

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

3.5. Производительность

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

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

4. Вывод

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

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