Что именно означает неизменяемый, то есть каковы последствия того, что объект является изменяемым или неизменяемым? В частности, почему Stringнеизменяемые объекты Java?
Я понимаю, что StringBuilder тип - это что-то вроде изменяемого эквивалента String. Когда я должен использовать StringBuilder вместо String, и наоборот?
Переведено автоматически
Ответ 1
Неизменяемый означает, что после завершения выполнения конструктора для объекта этот экземпляр не может быть изменен.
Это полезно, поскольку означает, что вы можете передавать ссылки на объект по кругу, не беспокоясь о том, что кто-то другой изменит его содержимое. Особенно при работе с параллелизмом, нет проблем с блокировкой объектов, которые никогда не меняются
Foo не нужно беспокоиться о том, что вызывающий в getValue() может изменить текст в строке.
Если вы представите класс, аналогичный Foo, но с StringBuilder а не a String в качестве члена, вы увидите, что вызывающий getValue() сможет изменять StringBuilder атрибут Foo экземпляра.
Также остерегайтесь различных видов неизменяемости, которые вы можете обнаружить: Эрик Липперт написал об этом в статье в блоге. В принципе, у вас могут быть объекты, интерфейс которых неизменяем, но за кулисами фактическое изменяемое частное состояние (и, следовательно, не может быть безопасно передано потокам).
Ответ 2
Неизменяемый объект - это объект, в котором внутренние поля (или, по крайней мере, все внутренние поля, влияющие на его внешнее поведение) не могут быть изменены.
У неизменяемых строк много преимуществ:
Производительность: Выполните следующую операцию:
Stringsubstring= fullstring.substring(x,y);
Базовый C для метода substring(), вероятно, выглядит примерно так:
// Assume string is stored like this: struct String { char* characters; unsigned int length; };
// Passing pointers because Java is pass-by-reference struct String* substring(struct String* in, unsigned int begin, unsigned int end) { struct String* out = malloc(sizeof(struct String)); out->characters = in->characters + begin; out->length = end - begin; return out; }
Обратите внимание, что ни один из символов не нужно копировать! Если бы объект String был изменяемым (символы могли измениться позже), то вам пришлось бы скопировать все символы, в противном случае изменения символов в подстроке были бы отражены в другой строке позже.
Параллелизм: если внутренняя структура неизменяемого объекта допустима, она всегда будет действительной. Нет никаких шансов, что разные потоки могут создать недопустимое состояние внутри этого объекта. Следовательно, неизменяемые объекты потокобезопасны.
Сборка мусора: сборщику мусора намного проще принимать логические решения относительно неизменяемых объектов.
Однако у неизменяемости есть и недостатки:
Производительность: Подождите, я думал, вы сказали, что производительность - это преимущество неизменяемости! Ну, иногда это так, но не всегда. Возьмите следующий код:
foo = foo.substring(0,4) + "a" + foo.substring(5); // foo is a String bar.replace(4,5,"a"); // bar is a StringBuilder
Обе строки заменяют четвертый символ буквой "a". Вторая часть кода не только более читабельна, но и быстрее. Посмотрите, как вам пришлось бы выполнять базовый код для foo. С подстроками все просто, но теперь, поскольку в пятом пробеле уже есть символ и что-то еще может ссылаться на foo, вы не можете просто изменить это; вы должны скопировать всю строку целиком (конечно, часть этой функциональности абстрагирована в функции в реальном базовом C, но смысл здесь в том, чтобы показать код, который выполняется, в одном месте).
// The code that executes struct String* astring; chara='a'; astring->characters = &a; astring->length = 1; foo = concatenate(concatenate(slice(foo,0,4),astring),slice(foo,5,foo->length));
Обратите внимание, что concatenate вызывается дважды, что означает, что вся строка должна быть зациклена! Сравните это с кодом C для bar операции:
bar->characters[4] = 'a';
Очевидно, что операция с изменяемой строкой выполняется намного быстрее.
В заключение: в большинстве случаев вам нужна неизменяемая строка. Но если вам нужно много добавлять и вставлять в строку, вам нужна изменяемость для скорости. Если вы хотите получить преимущества от безопасности параллелизма и сборки мусора, ключ в том, чтобы сохранить ваши изменяемые объекты локальными для метода:
// This will have awful performance if you don't use mutable strings String join(String[] strings, String separator) { StringBuilder mutable; booleanfirst=true;
for(inti=0; i < strings.length; i++) { if(first) first = false; else mutable.append(separator);
mutable.append(strings[i]); }
return mutable.toString(); }
Поскольку mutable объект является локальной ссылкой, вам не нужно беспокоиться о безопасности параллелизма (только один поток когда-либо касается его). И поскольку на него больше нигде нет ссылок, он выделяется только в стеке, поэтому он освобождается, как только завершается вызов функции (вам не нужно беспокоиться о сборке мусора). И вы получаете все преимущества производительности как изменяемости, так и неизменяемости.
Ответ 3
На самом деле String не является неизменяемым, если вы используете определение википедии, предложенное выше.
Состояние String действительно изменяет конструкцию post . Взгляните на метод hashcode() . String кэширует значение hashcode в локальном поле, но не вычисляет его до первого вызова hashcode() . Эта ленивая оценка хэш-кода ставит String в интересное положение как неизменяемый объект, состояние которого изменяется, но нельзя заметить, что оно изменилось, без использования отражения.
Итак, возможно, определение immutable должно быть объектом, за которым нельзя наблюдать изменения.
Если состояние неизменяемого объекта изменяется после его создания, но никто не может его видеть (без отражения), остается ли объект неизменяемым?
Ответ 4
Неизменяемые объекты - это объекты, которые нельзя изменить программно. Они особенно хороши для многопоточных сред или других сред, где более одного процесса могут изменять значения в объекте.
Однако просто для пояснения, StringBuilder на самом деле является изменяемым объектом, а не неизменяемым. Обычная строка java является неизменяемой (это означает, что после ее создания вы не можете изменить базовую строку без изменения объекта).
Например, предположим, что у меня есть класс с именем ColoredString, который имеет строковое значение и цвет строки:
В этом примере ColoredString называется изменяемым, потому что вы можете изменить (мутировать) одно из его ключевых свойств без создания нового класса ColoredString . Причина, по которой это может быть плохо, заключается, например, в том, что у вас есть приложение с графическим интерфейсом, которое имеет несколько потоков, и вы используете ColoredStrings для печати данных в окне. Если у вас есть экземпляр ColoredString, который был создан как
newColoredString("Blue", "This is a blue string!");
Тогда можно было бы ожидать, что строка всегда будет "синей". Однако, если другой поток получил доступ к этому экземпляру и вызвал
blueString.setColor("Red");
У вас внезапно и, вероятно, неожиданно появилась бы "красная" строка, когда вы хотели "синюю". Из-за этого неизменяемые объекты почти всегда предпочтительнее при передаче экземпляров объектов по кругу. Когда у вас есть случай, когда изменяемые объекты действительно необходимы, тогда вы обычно защищаете объект, передавая копии только из вашей конкретной области управления.
Напомним, что в Java java.lang.String - это неизменяемый объект (его нельзя изменить после его создания) и java.lang.StringBuilder - это изменяемый объект, потому что его можно изменить без создания нового экземпляра.