«1. Введение
В Java строки неизменяемы. Очевидный вопрос, часто встречающийся в интервью, звучит так: «Почему строки спроектированы как неизменяемые в Java?»
Джеймсу Гослингу, создателю Java, однажды в интервью задали вопрос, когда следует использовать неизменяемые, на что он ответил. :
I would use an immutable whenever I can.
Он также поддерживает свой аргумент, заявляя о функциях, которые предоставляет неизменяемость, таких как кэширование, безопасность, простое повторное использование без репликации и т. д.
В этом руководстве мы подробнее рассмотрим, почему разработчики языка Java решили сохранить неизменность строк. .
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 из пула, тем самым экономя важный ресурс памяти.
У нас есть отдельная статья, посвященная 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 сделать этот класс неизменяемым.