Я не мог понять причину этого. Я всегда использую класс String, как и другие разработчики, но когда я изменяю его значение, создается новый экземпляр String.
В чем может быть причина неизменности класса String в Java?
Я знаю, что есть некоторые альтернативы, такие как StringBuffer или StringBuilder. Это просто любопытство.
Ответы:
совпадение
Java была определена с самого начала из соображений параллелизма. Как часто упоминалось, общие переменные являются проблематичными. Одна вещь может изменить другую за спиной другого потока без того, чтобы этот поток знал об этом.
Существует множество многопоточных ошибок C ++, которые возникли из-за общей строки - когда один модуль думал, что его можно безопасно изменить, когда другой модуль в коде сохранил указатель на него, и ожидал, что он останется прежним.
«Решение» этого заключается в том, что каждый класс создает защитную копию изменяемых объектов, которые ему передаются. Для изменяемых строк это O (n), чтобы сделать копию. Для неизменяемых строк создание копии - это O (1), потому что это не копия, а тот же объект, который не может измениться.
В многопоточной среде неизменные объекты всегда можно безопасно разделить между собой. Это приводит к общему снижению использования памяти и улучшает кеширование памяти.
Безопасность
Часто в качестве аргументов для конструкторов передаются строки - сетевые соединения и протоколы - два, которые легче всего приходят на ум. Возможность изменить это в неопределенное время позже при выполнении может привести к проблемам с безопасностью (функция думала, что она подключалась к одной машине, но была перенаправлена на другую, но все в объекте выглядит так, как будто оно подключено к первой ... это даже та же строка).
Java позволяет использовать отражение - и параметры для этого являются строками. Опасность передачи строки, которая может быть изменена путем к другому методу, который отражает. Это очень плохо.
Ключи к хэшу
Хеш-таблица является одной из наиболее часто используемых структур данных. Ключи к структуре данных очень часто являются строками. Наличие неизменяемых строк означает, что (как указано выше) хеш-таблица не должна делать копию хеш-ключа каждый раз. Если бы строки были изменяемыми, а хеш-таблица этого не делала, можно было бы что-то изменить на расстоянии.
Способ работы объекта в Java заключается в том, что все имеет ключ хеша (доступ к которому осуществляется с помощью метода hashCode ()). Наличие неизменяемой строки означает, что hashCode может быть кэширован. Учитывая, как часто строки используются в качестве ключей для хэша, это обеспечивает значительное повышение производительности (вместо того, чтобы каждый раз пересчитывать хэш-код).
Подстроки
Если String будет неизменным, базовый массив символов, который поддерживает структуру данных, также будет неизменным. Это позволяет выполнять определенные оптимизации
substring
метода (они не обязательно выполняются - это также создает возможность некоторых утечек памяти).Если вы делаете:
Значение
bar
«миля». Тем не менее, какfoo
иbar
может быть поддержан тот же массив символов, уменьшая экземпляра более массивов символов или копирование его - просто используя различные начальные и конечные точки в пределах строки.Теперь недостатком этого (утечка памяти) является то, что если бы у вас была строка длиной 1 КБ и была взята подстрока первого и второго символа, она также была бы подкреплена массивом символов 1 КБ. Этот массив останется в памяти, даже если исходная строка, которая имеет значение всего массива символов, была собрана сборщиком мусора.
Это можно увидеть в String из JDK 6b14 (следующий код взят из источника GPL v2 и используется в качестве примера)
Обратите внимание, как подстрока использует конструктор String уровня пакета, который не включает в себя копирование массива и будет намного быстрее (за счет возможного хранения некоторых больших массивов - хотя и без дублирования больших массивов).
Обратите внимание, что приведенный выше код предназначен для Java 1.6. Способ реализации конструктора подстроки был изменен в Java 1.7, как описано в документе « Изменения во внутреннем представлении String, сделанном в Java 1.7.0_06» - проблема, связанная с утечкой памяти, о которой я упоминал выше. Скорее всего, Java не рассматривался как язык с большим количеством манипуляций со строками, поэтому повышение производительности подстроки было хорошей вещью. Теперь, когда огромные XML-документы хранятся в строках, которые никогда не собираются, это становится проблемой ... и, таким образом, происходит переход к использованию
String
не того же базового массива с подстрокой, чтобы более крупный массив символов можно было собирать быстрее.Не злоупотребляйте стеком
Один может передать значение строки вокруг вместо ссылки на незыблемую строку , чтобы избежать проблем с изменчивостью. Однако с большими строками передача этого в стек будет ... оскорбительной для системы (помещать целые XML-документы в виде строк в стек и затем снимать их или продолжать передавать их вместе ...).
Возможность дедупликации
Конечно, это не было первоначальной мотивацией того, почему строки должны быть неизменяемыми, но когда кто-то смотрит на рациональное объяснение того, почему неизменяемые строки - хорошая вещь, это, безусловно, нужно учитывать.
Любой, кто немного работал со строками, знает, что они могут высосать память. Это особенно верно, когда вы делаете такие вещи, как извлечение данных из баз данных, которые остаются на некоторое время. Много раз с этими укусами, они снова и снова становятся одной и той же строкой (по одному разу для каждой строки).
В Java 8, обновление 20, JEP 192 (мотивация приведена выше) реализуется для решения этой проблемы. Не вдаваясь в детали того, как работает дедупликация строк, важно, чтобы сами строки были неизменными. Вы не можете дедуплицировать StringBuilders, потому что они могут измениться, и вы не хотите, чтобы кто-то что-то изменял из-под вас. Неизменяемые строки (связанные с этим пулом строк) означают, что вы можете пройти через них, и если вы найдете две одинаковые строки, вы можете указать одну строковую ссылку на другую и позволить сборщику мусора поглотить вновь неиспользованную.
Другие языки
Цель C (которая предшествует Java) имеет
NSString
иNSMutableString
.C # и .NET сделали те же самые варианты дизайна строки по умолчанию, являющейся неизменной.
Строки Lua также неизменны.
Питон также.
Исторически, Lisp, Scheme, Smalltalk все интернируют строку и поэтому имеют ее неизменяемость. Более современные динамические языки часто используют строки некоторым образом, который требует, чтобы они были неизменяемыми (это может быть не строка , но она неизменна).
Заключение
Эти конструктивные соображения были сделаны снова и снова на множестве языков. По общему мнению, неизменяемые строки при всей их неловкости лучше альтернатив и приводят к лучшему коду (меньше ошибок) и в целом к более быстрым исполняемым файлам.
источник
Причины, которые я могу вспомнить:
Функция пула строк без возможности сделать строку неизменной вообще невозможна, потому что в случае пула строк один строковый объект / литерал, например, «XYZ», будет ссылаться на многие ссылочные переменные, поэтому, если любая из них изменит значение, на другие будут автоматически влиять ,
Строка широко используется в качестве параметра для многих классов Java, например, для открытия сетевого подключения, для открытия подключения к базе данных, открытия файлов. Если String не является неизменяемым, это может привести к серьезной угрозе безопасности.
Неизменность позволяет String кэшировать свой хэш-код.
Делает это потокобезопасным.
источник
1) Струнный пул
Java-дизайнер знает, что String будет наиболее часто используемым типом данных во всех видах Java-приложений, и поэтому он хотел оптимизировать его с самого начала. Одним из ключевых шагов в этом направлении была идея хранения строковых литералов в строковом пуле. Цель состояла в том, чтобы уменьшить временные объекты String, разделяя их, и для того, чтобы делиться, они должны быть из неизменного класса. Вы не можете поделиться изменяемым объектом с двумя сторонами, которые неизвестны друг другу. Давайте рассмотрим гипотетический пример, где две ссылочные переменные указывают на один и тот же объект String:
Теперь, если s1 изменяет объект с «Java» на «C ++», ссылочная переменная также получает значение s2 = «C ++», о котором она даже не знает. Делая String неизменным, это деление строкового литерала стало возможным. Короче говоря, ключевая идея пула String не может быть реализована без окончательного или неизменного String в Java.
2) Безопасность
Java имеет четкую цель с точки зрения обеспечения безопасной среды на каждом уровне обслуживания, и String имеет решающее значение для всей этой системы безопасности. Строка широко используется в качестве параметра для многих классов Java, например, для открытия сетевого подключения вы можете передать хост и порт как String, для чтения файлов в Java вы можете передать путь файлов и каталогов как String, а для открытия подключения к базе данных вы можете передать URL базы данных в виде строки. Если бы String не был неизменным, пользователь мог бы предоставить доступ к определенному файлу в системе, но после аутентификации он может изменить PATH на что-то другое, это может вызвать серьезные проблемы с безопасностью. Аналогично, при подключении к базе данных или любому другому компьютеру в сети, изменяющееся значение String может создавать угрозы безопасности. Изменяемые строки также могут вызвать проблемы с безопасностью в Reflection,
3) Использование строки в механизме загрузки классов
Другая причина сделать String final или Immutable была вызвана тем фактом, что он активно использовался в механизме загрузки классов. Поскольку String не является неизменным, злоумышленник может воспользоваться этим фактом, и запрос на загрузку стандартных классов Java, например, java.io.Reader, может быть изменен на вредоносный класс com.unknown.DataStolenReader. Сохраняя String final и неизменным, мы можем по крайней мере быть уверенными, что JVM загружает правильные классы.
4) Преимущества многопоточности
Поскольку параллелизм и многопоточность были ключевым предложением Java, имело смысл подумать о безопасности потоков в объектах String. Поскольку ожидалось, что String будет широко использоваться, его неизменность означает отсутствие внешней синхронизации, что означает более чистый код, включающий совместное использование String между несколькими потоками. Эта единственная особенность делает намного более сложным, запутанным и подверженным ошибкам параллельное кодирование намного проще. Поскольку String является неизменным, и мы просто разделяем его между потоками, это приводит к более читаемому коду.
5) Оптимизация и производительность
Теперь, когда вы делаете класс Immutable, вы заранее знаете, что этот класс не изменится после его создания. Это гарантирует открытый путь для многих оптимизаций производительности, например, кеширования. Сама строка знает, что менять не собираюсь, поэтому String кеширует свой хеш-код. Он даже вычисляет хэш-код лениво и после его создания просто кеширует его. В простом мире, когда вы в первый раз вызываете метод hashCode () любого объекта String, он вычисляет хеш-код, и все последующие вызовы hashCode () возвращают уже рассчитанное кэшированное значение. Это приводит к хорошему приросту производительности, поскольку String интенсивно используется в картах, основанных на хэше, например, Hashtable и HashMap. Кэширование хеш-кода было невозможно без того, чтобы сделать его неизменным и окончательным, так как это зависит от содержимого самой строки.
источник
Виртуальная машина Java выполняет несколько оптимизаций относительно строковых операций, которые не могли бы быть выполнены иначе. Например, если у вас была строка со значением «Mississippi» и вы присвоили «Mississippi» .substring (0, 4) другой строке, насколько вам известно, была сделана копия первых четырех символов, чтобы сделать «Miss» , Чего вы не знаете, так это того, что обе они используют одну и ту же исходную строку «Миссисипи», причем одна является владельцем, а другая - ссылкой на эту строку с позиции 0 до 4. (Ссылка на владельца не позволяет владельцу собирать сборщик мусора, когда владелец выходит за рамки)
Это тривиально для такой маленькой строки, как «Миссисипи», но с более крупными строками и несколькими операциями не нужно копировать строку, что значительно экономит время! Если бы строки были изменяемыми, вы бы не смогли этого сделать, потому что изменение оригинала также повлияло бы на «копии» подстроки.
Кроме того, как упоминает Донал, преимущество было бы значительно ослаблено его недостатком. Представьте, что вы пишете программу, которая зависит от библиотеки, и используете функцию, которая возвращает строку. Как вы можете быть уверены, что это значение останется постоянным? Чтобы ничего подобного не происходило, вам всегда нужно создавать копию.
Что делать, если у вас два потока, разделяющих одну и ту же строку? Вы не хотели бы читать строку, которая в настоящее время переписывается другим потоком, не так ли? Следовательно, String должен был бы быть потокобезопасным, что, будучи его общим классом, сделало бы практически каждую программу Java намного медленнее. В противном случае вам нужно будет сделать копию для каждого потока, для которого требуется эта строка, или вам придется поместить код, использующий эту строку, в блок синхронизации, оба из которых только замедляют вашу программу.
По всем этим причинам это было одно из первых решений, принятых для Java, чтобы отличаться от C ++.
источник
Причиной неизменности строки является согласованность с другими примитивными типами в языке. Если у вас есть
int
значение 42 и вы добавляете к нему значение 1, вы не изменяете значение 42. Вы получаете новое значение 43, которое совершенно не связано с начальными значениями. Мутирующие примитивы, кроме строки, не имеют концептуального смысла; и поскольку такие программы, которые обрабатывают строки как неизменяемые, часто легче рассуждать и понимать.Более того, Java действительно предоставляет как изменяемые, так и неизменные строки, как вы видите
StringBuilder
; на самом деле неизменной является только строка по умолчанию . Если вы хотите передавать ссылкиStringBuilder
везде, вы можете это сделать. Java использует отдельные типы (String
иStringBuilder
) для этих понятий, потому что у нее нет поддержки для выражения изменчивости или ее отсутствия в своей системе типов. В языках, которые поддерживают неизменяемость в своих системах типов (например, C ++const
), часто существует один тип строки, который служит обеим целям.Да, наличие неизменяемой строки позволяет реализовать некоторые оптимизации, характерные для неизменяемых строк, например интернирование, и позволяет передавать ссылки на строки без синхронизации между потоками. Однако это путает механизм с намеченной целью языка с простой и непротиворечивой системой типов. Я сравниваю это с тем, как все думают о сборе мусора неправильно; сборка мусора - это не «восстановление неиспользуемой памяти»; это «симуляция компьютера с неограниченной памятью» . Обсуждаемая оптимизация производительности - это то, что делается для того, чтобы цель неизменяемых строк работала на реальных машинах; не причина для таких строк быть неизменными в первую очередь.
источник
43 = 6
и ожидать, что число 43 будет означать то же самое, что и число 6.i
, а не 42. Подумайтеstring s = "Hello "; s += "World";
. Вы мутировали значение переменнойs
. Но строки"Hello "
,"World"
и"Hello World"
неизменны.Неизменность означает, что константы, содержащиеся в принадлежащих вам классах, не могут быть изменены. Классы, которые вам не принадлежат, включают в себя те, которые лежат в основе реализации Java, и строки, которые не следует изменять, включают такие вещи, как маркеры безопасности, адреса служб и т. Д. Вы действительно не должны иметь возможность изменять эти виды вещей (и это применяется вдвойне при работе в режиме песочницы).
Если String не был неизменным, каждый раз, когда вы извлекали его из некоторого контекста, который не хотел, чтобы содержимое строки изменялось у него под ногами, вам приходилось брать копию «на всякий случай». Это становится очень дорогим.
источник
String
. Но, например,Array
s все же изменчивы. Итак, почему ониString
неизменны, аArray
нет. И если неизменность так важна, то почему в Java так сложно создавать неизменные объекты и работать с ними?Представьте себе систему, в которой вы принимаете некоторые данные, проверяете их правильность и затем передаете их (например, для хранения в БД).
Предполагается, что данные имеют
String
длину не менее 5 символов. Ваш метод выглядит примерно так:Теперь мы можем согласиться с тем, что при
storeInDatabase
вызове здесьinput
будет соответствовать требованию. Но если бы онString
был изменяемым, то вызывающая сторона могла бы изменитьinput
объект (из другого потока) сразу после его проверки и до того, как он был сохранен в базе данных . Это потребует хорошего времени и, вероятно, не всегда будет работать хорошо, но иногда он сможет заставить вас хранить недопустимые значения в базе данных.Неизменяемые типы данных являются очень простым решением этой (и множества связанных) проблем: всякий раз, когда вы проверяете какое-либо значение, вы можете зависеть от того факта, что проверенное условие все еще остается верным позже.
источник
input
вhandle
методе уже не является слишком долго (независимо от того , что оригиналinput
есть), это было бы просто выбросить исключение. Вы создаете новый вход перед вызовом метода. Это не проблема.В общем, вы встретите типы значений и ссылочные типы . С типом значения вы не заботитесь об объекте, который его представляет, вы заботитесь о значении. Если я дам вам значение, вы ожидаете, что это значение останется прежним. Вы не хотите, чтобы это внезапно изменилось. Число 5 является значением. Вы не ожидаете, что он внезапно изменится на 6. Строка «Hello» является значением. Вы не ожидаете, что он внезапно изменится на «P *** off».
Со ссылочными типами вы заботитесь об объекте и ожидаете, что он изменится. Например, вы часто будете ожидать изменения массива. Если я дам вам массив, и вы захотите сохранить его таким, какой он есть, вы должны либо довериться мне, чтобы я не изменил его, либо сделать копию.
С помощью строкового класса Java разработчики должны были принять решение: лучше ли строки вести себя как тип значения или они должны вести себя как ссылочный тип? В случае строк Java было принято решение, что они должны быть типами значений, что означает, что, поскольку они являются объектами, они должны быть неизменяемыми объектами.
Противоположное решение могло быть принято, но, по моему мнению, это вызвало бы много головных болей. Как уже было сказано, многие языки приняли одно и то же решение и пришли к такому же выводу. Исключением является C ++, который имеет один класс строк, и строки могут быть постоянными или непостоянными, но в C ++, в отличие от Java, параметры объекта могут передаваться как значения, а не как ссылки.
источник
Я действительно удивлен, что никто не указал на это.
Ответ: Это не принесет вам существенной пользы, даже если оно будет изменчивым. Это не принесет вам столько пользы, сколько вызовет дополнительные проблемы. Давайте рассмотрим два наиболее распространенных случая мутации:
Изменение одного символа строки
Поскольку каждый символ в строке Java занимает 2 или 4 байта, спросите себя, получите ли вы что-нибудь, если сможете изменить существующую копию?
В сценарии вы заменяете 2-байтовый символ 4-байтовым (или наоборот), вам нужно сместить оставшуюся часть строки на 2 байта влево или вправо. Что ничем не отличается от копирования всей строки с вычислительной точки зрения.
Это также действительно нерегулярное поведение, которое обычно нежелательно. Представьте, что кто-то тестирует приложение с текстом на английском языке, и когда приложение попадает в другие страны, например в Китай, все начинает работать странным образом.
Присоединение другой строки (или символа) к существующей
Если у вас есть две произвольные строки, они находятся в двух разных местах памяти. Если вы хотите изменить первое, добавив второе, вы не можете просто запросить дополнительную память в конце первой строки, поскольку она, вероятно, уже занята.
Вы должны скопировать объединенную строку в совершенно новое место, которое точно так же, как если бы обе строки были неизменными.
Если вы хотите эффективно добавлять, вы можете использовать его
StringBuilder
, который резервирует довольно много места в конце строки, просто для возможного добавления в будущем.источник
они дорогие, и сохранение их неизменяемыми допускает такие вещи, как подстроки, разделяющие массив байтов основной строки. (увеличение скорости также не требует создания нового байтового массива и копирования)
безопасность - не хотел бы, чтобы ваш пакет или код класса был переименован
[убрал старый 3, посмотрел на StringBuilder src - он не делит память со строкой (до изменения), я думаю, что это было в 1.3 или 1.4]
кеш хэш-код
для взаимозаменяемых строк используйте SB (компоновщик или буфер при необходимости)
источник
Строки должны были быть примитивным типом данных в Java. Если бы они были, то строки по умолчанию были бы изменяемыми, а последнее ключевое слово генерировало бы неизменные строки. Изменяемые строки полезны, поэтому существует множество хаков для изменяемых строк в классах stringbuffer, stringbuilder и charsequence.
источник