Почему строки не могут быть изменяемыми в Java и .NET? - PullRequest
185 голосов
/ 18 сентября 2008

Почему они решили сделать строку неизменной в Java и .NET (и некоторых других языках)? Почему они не сделали его изменчивым?

Ответы [ 17 ]

202 голосов
/ 18 сентября 2008

Согласно Эффективная Java , глава 4, стр. 73, 2-е издание:

"Для этого есть много веских причин: неизменные классы легче разрабатывать, реализовывать и использовать не изменяемые классы Они менее склонны к ошибке и более безопасны.

[...]

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

[...]

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

[...]

Другие маленькие точки из той же главы:

Вы можете не только делиться неизменными объектами, но и делиться своими внутренними объектами.

[...]

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

[...]

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

102 голосов
/ 18 сентября 2008

Есть как минимум две причины.

Первый - безопасность http://www.javafaq.nu/java-article1060.html

Основная причина, почему сделал String неизменной была безопасность. Посмотри на это пример: у нас есть метод открытия файла с проверкой логина. Мы передаем строку в этот метод для обработки аутентификации что необходимо перед звонком будет передано в ОС. Если строка была изменчивый можно было как-то изменить его содержание после проверка подлинности до получения ОС запрос из программы, то это Можно запросить любой файл. Так что если у вас есть право открыть текстовый файл в каталог пользователя, но потом на лету когда каким-то образом вам удастся изменить имя файла вы можете запросить открыть файл passwd или любой другой. Затем файл может быть изменен, и он будет можно войти прямо в ОС.

Второй - Эффективность памяти http://hikrish.blogspot.com/2006/07/why-string-class-is-immutable.html

JVM поддерживает "String" Бассейн ». Для достижения памяти эффективность, JVM будет ссылаться на строку объект из пула. Это не создаст новые строковые объекты. Итак, всякий раз, когда вы создаете новый строковый литерал, JVM проверим в бассейне уже существует или нет. Если уже присутствовать в бассейне, просто дать ссылка на тот же объект или создать новый объект в бассейне. Там будет много ссылок указывают на то же самое Строковые объекты, если кто-то изменяет значение, это повлияет на все Рекомендации. Итак, солнце решило сделать это неизменны.

57 голосов
/ 21 марта 2009

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

Thead Safety:

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

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

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

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

Что еще более важно, неизменяемые строки позволяют им обмениваться своими внутренними данными. Для многих строковых операций это означает, что базовый массив символов не нужно копировать. Например, скажем, вы хотите взять пять первых символов строки. В Java вы бы вызвали myString.substring (0,5). В этом случае метод substring () просто создает новый объект String, который разделяет базовый тип char [] myString, но кто знает, что он начинается с индекса 0 и заканчивается индексом 5 этого char []. Чтобы поместить это в графическую форму, вы должны получить следующее:

 |               myString                  |
 v                                         v
"The quick brown fox jumps over the lazy dog"   <-- shared char[]
 ^   ^
 |   |  myString.substring(0,5)

Это делает такие операции чрезвычайно дешевыми, и O (1), поскольку операция не зависит ни от длины исходной строки, ни от длины подстроки, которую мы должны извлечь. Такое поведение также имеет некоторые преимущества памяти, так как многие строки могут совместно использовать свой основной символ [].

28 голосов
/ 18 сентября 2008

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

11 голосов
/ 06 февраля 2009

Надо действительно спросить: «Почему Х должен быть изменчивым?» По умолчанию лучше использовать неизменяемость из-за преимуществ, уже упомянутых Princess Fluff . Исключением является то, что что-то изменчиво.

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

7 голосов
/ 27 марта 2009

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

Это довольно новая идея, что неизменность String решает проблемы с многопоточностью. Хммм ... У меня есть объект, который изменяется двумя разными потоками. Как мне решить это? синхронизировать доступ к объекту? Naawww ... давайте не будем никого менять объект - это решит все наши проблемы с параллельным выполнением! Фактически, давайте сделаем все объекты неизменяемыми, а затем мы сможем удалить синхронизированный конструкт из языка Java.

Реальная причина (указанная другими выше) заключается в оптимизации памяти. В любом приложении достаточно часто использовать один и тот же строковый литерал. Фактически, это настолько распространено, что десятилетия назад многие компиляторы оптимизировали хранение только одного экземпляра строкового литерала. Недостаток этой оптимизации заключается в том, что код времени выполнения, который модифицирует строковый литерал, создает проблему, поскольку он изменяет экземпляр для всего другого кода, который разделяет его. Например, было бы нехорошо для функции где-то в приложении изменять строковый литерал «собака» на «кошка». Printf ("собака") приведет к тому, что "кошка" будет записана на стандартный вывод. По этой причине должен быть способ защиты от кода, который пытается изменить строковые литералы (то есть сделать их неизменяемыми). Некоторые компиляторы (при поддержке со стороны ОС) могли бы выполнить это, поместив строковый литерал в специальный сегмент памяти только для чтения, который мог вызвать ошибку памяти, если была предпринята попытка записи.

В Java это называется интернированием. Компилятор Java здесь просто следует стандартной оптимизации памяти, проводимой компиляторами в течение десятилетий. И для решения той же проблемы этих строковых литералов, изменяемых во время выполнения, Java просто делает класс String неизменяемым (то есть не дает вам установщиков, которые позволили бы вам изменять содержимое String). Строки не должны быть неизменными, если интернирование строковых литералов не происходит.

7 голосов
/ 18 сентября 2008

String не является примитивным типом, но обычно вы хотите использовать его с семантикой значения, т. Е. Как со значением.

Значение, которому вы можете доверять, не изменится за вашей спиной. Если вы напишите: String str = someExpr(); Вы не хотите, чтобы это изменилось, если вы не делаете что-то с str.

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

7 голосов
/ 18 сентября 2008

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

6 голосов
/ 07 марта 2015

Я знаю, что это удар, но ... Они действительно неизменны? Подумайте о следующем.

public static unsafe void MutableReplaceIndex(string s, char c, int i)
{
    fixed (char* ptr = s)
    {
        *((char*)(ptr + i)) = c;
    }
}

...

string s = "abc";
MutableReplaceIndex(s, '1', 0);
MutableReplaceIndex(s, '2', 1);
MutableReplaceIndex(s, '3', 2);
Console.WriteLine(s); // Prints 1 2 3

Вы даже можете сделать это методом расширения.

public static class Extensions
{
    public static unsafe void MutableReplaceIndex(this string s, char c, int i)
    {
        fixed (char* ptr = s)
        {
            *((char*)(ptr + i)) = c;
        }
    }
}

Что делает следующую работу

s.MutableReplaceIndex('1', 0);
s.MutableReplaceIndex('2', 1);
s.MutableReplaceIndex('3', 2);

Заключение: они находятся в неизменном состоянии, известном компилятору. Конечно, вышесказанное относится только к строкам .NET, так как в Java нет указателей. Однако строка может быть полностью изменяемой, используя указатели в C #. Это не то, как указатели предназначены для использования, практического использования или безопасного использования; однако это возможно, таким образом, изгибая все «изменяемое» правило. Обычно вы не можете изменять индекс непосредственно для строки, и это единственный способ. Есть способ, которым это может быть предотвращено путем запрета экземпляров указателей на строки или создания копии, когда на указатель указана строка, но это не делается, что делает строки в C # не полностью неизменными.

3 голосов
/ 23 октября 2010

Неизменность не так тесно связана с безопасностью. Для этого, по крайней мере в .NET, вы получаете класс SecureString.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...