Что подразумевается под неизменным?
Это может быть самый тупой вопрос, который когда-либо задавался, но я думаю, что это полная путаница для новичка.
- Может кто-нибудь уточнить, что подразумевается под неизменным?
- Почему
String
неизменный? - Каковы преимущества / недостатки неизменяемых объектов?
- Зачем должен изменяться объект, такой как
StringBuilder
быть предпочтительнее строки и наоборот?
Хороший пример (на Java) будет по достоинству оценен.
17 ответов
Неизменяемый означает, что, как только конструктор для объекта завершит выполнение, этот экземпляр не может быть изменен.
Это полезно, так как это означает, что вы можете передавать ссылки на объект, не беспокоясь о том, что кто-то еще изменит его содержимое. Особенно при работе с параллелизмом нет проблем с блокировкой объектов, которые никогда не меняются
например
class Foo
{
private final String myvar;
public Foo(final String initialValue)
{
this.myvar = initialValue;
}
public String getValue()
{
return this.myvar;
}
}
Foo
не нужно беспокоиться, что звонящий getValue()
может изменить текст в строке.
Если вы представляете себе подобный класс Foo
, но с StringBuilder
а не String
как участник, вы можете видеть, что вызывающий абонент getValue()
сможет изменить StringBuilder
атрибут Foo
пример.
Также остерегайтесь различных видов неизменности, которые вы можете найти: Эрик Липперт написал статью об этом в блоге. По сути, вы можете иметь объекты, чей интерфейс является неизменным, но за кулисами фактически измененное частное состояние (и, следовательно, не может быть безопасно разделено между потоками).
Неизменяемый объект - это объект, в котором внутренние поля (или, по крайней мере, все внутренние поля, которые влияют на его внешнее поведение) не могут быть изменены.
Есть много преимуществ для неизменяемых строк:
Производительность: выполните следующую операцию:
String substring = 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
Обе строки заменяют четвертый символ буквой "а". Второй фрагмент кода не только более читабелен, но и быстрее. Посмотрите, как вам придется делать основной код для foo. Подстроки просты, но теперь, когда в пятой позиции уже есть символ и что-то еще может ссылаться на foo, вы не можете просто изменить его; Вы должны скопировать всю строку (конечно, некоторые из этих функций абстрагированы в функции в реальном базовом C, но суть здесь в том, чтобы показать код, который выполняется все в одном месте).
struct String* concatenate(struct String* first, struct String* second)
{
struct String* new = malloc(sizeof(struct String));
new->length = first->length + second->length;
new->characters = malloc(new->length);
int i;
for(i = 0; i < first->length; i++)
new->characters[i] = first->characters[i];
for(; i - first->length < second->length; i++)
new->characters[i] = second->characters[i - first->length];
return new;
}
// The code that executes
struct String* astring;
char a = '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;
boolean first = true;
for(int i = 0; i < strings.length; i++)
{
if(!first) first = false;
else mutable.append(separator);
mutable.append(strings[i]);
}
return mutable.toString();
}
Так как mutable
Объект является локальной ссылкой, вам не нужно беспокоиться о безопасности параллелизма (когда-либо к нему обращается только один поток). А поскольку на него больше нигде нет ссылок, он размещается только в стеке, поэтому он освобождается после завершения вызова функции (вам не нужно беспокоиться о сборке мусора). И вы получаете все преимущества производительности как изменчивости, так и неизменности.
На самом деле String не является неизменным, если вы используете определение Википедии, предложенное выше.
Состояние String меняет пост-конструкцию. Взгляните на метод hashcode(). String кэширует значение хеш-кода в локальном поле, но не вычисляет его до первого вызова hashcode(). Эта ленивая оценка хеш-кода ставит String в интересную позицию как неизменный объект, состояние которого изменяется, но нельзя наблюдать, как он изменился без использования отражения.
Так что, возможно, определение неизменяемости должно быть объектом, который не может быть изменен.
Если состояние изменяется в неизменяемом объекте после того, как он был создан, но никто не может видеть его (без отражения), остается ли объект неизменным?
Неизменяемые объекты - это объекты, которые нельзя изменить программно. Они особенно хороши для многопоточных сред или других сред, где более чем один процесс может изменять (изменять) значения в объекте.
Однако, чтобы уточнить, StringBuilder на самом деле является изменяемым объектом, а не неизменным. Обычная строка Java является неизменной (это означает, что после ее создания вы не можете изменить базовую строку без изменения объекта).
Например, предположим, что у меня есть класс ColoredString, который имеет значение String и цвет String:
public class ColoredString {
private String color;
private String string;
public ColoredString(String color, String string) {
this.color = color;
this.string = string;
}
public String getColor() { return this.color; }
public String getString() { return this.string; }
public void setColor(String newColor) {
this.color = newColor;
}
}
В этом примере ColoredString называется изменяемой, поскольку вы можете изменить (изменить) одно из его ключевых свойств, не создавая новый класс ColoredString. Причина, по которой это может быть плохо, например, допустим, у вас есть приложение с графическим интерфейсом, которое имеет несколько потоков, и вы используете ColoredStrings для печати данных в окне. Если у вас есть экземпляр ColoredString, который был создан как
new ColoredString("Blue", "This is a blue string!");
Тогда вы ожидаете, что строка всегда будет синей. Если другой поток, однако, получил этот экземпляр и вызвал
blueString.setColor("Red");
Вы бы внезапно и, вероятно, неожиданно, теперь получили "красную" строку, когда хотели "голубую". Из-за этого неизменные объекты почти всегда предпочтительнее при передаче экземпляров объектов вокруг. Если у вас есть случай, когда изменяемые объекты действительно необходимы, тогда вы обычно защищаете объект, только передавая копии из вашей конкретной области контроля.
Напомним, что в Java java.lang.String является неизменяемым объектом (его нельзя изменить после создания), а java.lang.StringBuilder является изменяемым объектом, поскольку его можно изменить без создания нового экземпляра.
- В больших приложениях обычно строковые литералы занимают большие биты памяти. Таким образом, чтобы эффективно обрабатывать память, JVM выделяет область, называемую "пул констант строки".( Обратите внимание, что в памяти даже строка без ссылки содержит символ char[], int для его длины и другой для своего hashCode. Для числа напротив, требуется максимум восемь непосредственных байтов)
- Когда complier сталкивается с литералом String, он проверяет пул, чтобы увидеть, существует ли уже идентичный литерал. И если он найден, ссылка на новый литерал направляется на существующую строку, и новый объект "строка-строка" не создается (существующая строка просто получает дополнительную ссылку).
- Следовательно: изменяемость строк сохраняет память...
- Но когда любая из переменных меняет значение, На самом деле - это только их ссылка, а не значение в памяти (следовательно, оно не повлияет на другие переменные, ссылающиеся на него), как показано ниже....
String s1 = "Старая строка";
//s1 variable, refers to string in memory
reference | MEMORY |
variables | |
[s1] --------------->| "Old String" |
Строка s2 = s1;
//s2 refers to same string as s1
| |
[s1] --------------->| "Old String" |
[s2] ------------------------^
s1 = "Новая строка";
//s1 deletes reference to old string and points to the newly created one
[s1] -----|--------->| "New String" |
| | |
|~~~~~~~~~X| "Old String" |
[s2] ------------------------^
Исходная строка "в памяти" не изменилась, но ссылочная переменная была изменена так, чтобы она ссылалась на новую строку. И если бы у нас не было s2, "Старая строка" все еще была бы в памяти, но мы не сможем получить к ней доступ...
"неизменный" означает, что вы не можете изменить значение. Если у вас есть экземпляр класса String, любой вызываемый вами метод, который, кажется, изменяет значение, фактически создаст другую строку.
String foo = "Hello";
foo.substring(3);
<-- foo here still has the same value "Hello"
Чтобы сохранить изменения, вы должны сделать что-то вроде этого foo = foo.sustring(3);
Неизменность и изменчивость могут быть смешными, когда вы работаете с коллекциями. Подумайте, что произойдет, если вы используете изменяемый объект в качестве ключа для карты, а затем измените значение (совет: подумайте о equals
а также hashCode
).
java.time
Это может быть немного поздно, но чтобы понять, что такое неизменный объект, рассмотрим следующий пример из нового Java 8 Date and Time API ( java.time). Как вы, наверное, знаете, все объекты даты из Java 8 являются неизменяемыми, поэтому в следующем примере
LocalDate date = LocalDate.of(2014, 3, 18);
date.plusYears(2);
System.out.println(date);
Выход:
2014-03-18
Это печатает тот же год, что и начальная дата, потому что plusYears(2)
возвращает новый объект, поэтому старая дата остается неизменной, потому что это неизменный объект. После создания вы не можете его изменить, а переменная даты все еще указывает на него.
Таким образом, этот пример кода должен захватывать и использовать новый объект, созданный и возвращенный этим вызовом plusYears
,
LocalDate date = LocalDate.of(2014, 3, 18);
LocalDate dateAfterTwoYears = date.plusYears(2);
date.toString ()… 2014-03-18
dateAfterTwoYears.toString ()… 2016-03-18
Мне очень нравится объяснение из SCJP Sun Certified Programmer для Java 5 Учебное пособие.
Чтобы повысить эффективность использования памяти Java, JVM выделяет специальную область памяти, которая называется "Строка константных пулов". Когда компилятор встречает литерал String, он проверяет пул, чтобы увидеть, существует ли уже идентичная строка. Если совпадение найдено, ссылка на новый литерал направляется на существующую строку, и новый объект литерала строки не создается.
Объекты, которые являются неизменяемыми, не могут изменить свое состояние после того, как они были созданы.
Существует три основных причины, по которым вы всегда можете использовать неизменяемые объекты, и все это поможет уменьшить количество ошибок, которые вы вносите в свой код:
- Намного легче рассуждать о том, как работает ваша программа, когда вы знаете, что состояние объекта не может быть изменено другим методом.
- Неизменяемые объекты автоматически поточнобезопасны (при условии, что они опубликованы безопасно), поэтому никогда не будут причиной этих трудных для обнаружения ошибок многопоточности
- Неизменяемые объекты всегда будут иметь одинаковый хэш-код, поэтому их можно использовать в качестве ключей в HashMap (или аналогичных). Если хэш-код элемента в хеш-таблице должен был измениться, запись в таблице будет фактически потеряна, поскольку попытки найти его в таблице в конечном итоге будут искать в неправильном месте. Это главная причина того, что объекты String являются неизменяемыми - они часто используются в качестве ключей HashMap.
Есть также некоторые другие оптимизации, которые вы могли бы выполнить в коде, когда знаете, что состояние объекта является неизменным - например, кеширование вычисленного хэша - но это оптимизация и, следовательно, не столь интересная.
Одно из значений связано с тем, как значение хранится в компьютере. Например, для строки.Net это означает, что строку в памяти нельзя изменить. Когда вы думаете, что изменяете ее, вы фактически создаете новую строка в памяти и указание существующей переменной (которая является просто указателем на фактическую коллекцию символов где-то еще) на новую строку.
String s1="Hi";
String s2=s1;
s1="Bye";
System.out.println(s2); //Hi (if String was mutable output would be: Bye)
System.out.println(s1); //Bye
s1="Hi"
: объект s1
был создан со значением "Привет" в нем.
s2=s1
: объект s2
создается со ссылкой на объект s1.
s1="Bye"
: предыдущий s1
значение объекта не меняется, потому что s1
имеет тип String, а тип String является неизменяемым, вместо этого компилятор создает новый объект String со значением "Bye" и s1
ссылаются на это. здесь, когда мы печатаем s2
значение, результат будет "Привет", а не "Пока", потому что s2
ссылается на предыдущий s1
объект, который имел значение "Привет".
Неизменяемый означает, что после создания объекта, ни один из его членов не изменится. String
является неизменным, так как вы не можете изменить его содержание. Например:
String s1 = " abc ";
String s2 = s1.trim();
В приведенном выше коде строка s1 не изменилась, другой объект (s2
) был создан с использованием s1
,
Неизменяемые просто означают неизменяемые или неизменяемые. После создания строкового объекта его данные или состояние не могут быть изменены.
Рассмотрим приведенный ниже пример,
class Testimmutablestring{
public static void main(String args[]){
String s="Future";
s.concat(" World");//concat() method appends the string at the end
System.out.println(s);//will print Future because strings are immutable objects
}
}
Давайте разберемся с диаграммой ниже,
На этой диаграмме вы можете увидеть новый объект, созданный как "Мир будущего". Но не меняйте "Будущее".Because String is immutable
, s
, все еще обратитесь к "Будущему". Если вам нужно позвонить "Мир будущего",
String s="Future";
s=s.concat(" World");
System.out.println(s);//print Future World
Почему строковые объекты неизменяемы в Java?
Потому что в Java используется концепция строкового литерала. Предположим, есть 5 ссылочных переменных, все ссылаются на один объект "Будущее". Если одна ссылочная переменная изменит значение объекта, это повлияет на все ссылочные переменные. Вот почему строковые объекты неизменны в Java.
Что такое неизменный объект?
Согласно Справочной документации Java, "Объект считается неизменным, если его состояние не может измениться после его создания". Просто неизменный класс - это класс, свойства которого нельзя изменить после создания. Это означает, что при создании все данные экземпляра предоставляются и остаются неизменными до уничтожения объектов.
Почему неизменяемые объекты?
Неизменяемые классы отлично подходят для параллельных приложений. По своей неизменной природе они помогают поддерживать приложение в не поврежденном и последовательном поведении, так как состояние экземпляров не может быть изменено.
Обеспокоенность
Неизменяемые классы легко проектировать, разрабатывать и использовать, поскольку они более устойчивы к ошибкам и безопасны благодаря неизменности. Однако программисты неохотно используют их из-за ограничения повторного использования. Также это может оказать влияние на производительность при создании объекта, чем повторное использование. Однако с последними сложными компиляторами стоимость создания минимальна. Также предоставленные возможности и преимущество неизменности могут быть приняты за пределы возможности повторного использования в соответствии с необходимостью.
Как работает это приложение String?
Новый объект String создается с заданным значением, а ссылка обновляется до нового экземпляра, изолирующего старый объект.
Ссылка: http://www.devdummy.com/2017/09/immutable-objects-in-java.html
Однажды созданный, не может быть изменен. Рассмотрим класс, экземпляр которого может использоваться в качестве ключа для хеш-таблицы или аналогичного. Ознакомьтесь с рекомендациями Java.
Поскольку принятый ответ отвечает не на все вопросы. Я вынужден дать ответ через 11 лет и 6 месяцев.
Может кто-нибудь прояснить, что подразумевается под неизменным?
Надеюсь, вы имели в виду неизменяемый объект (потому что мы могли думать о неизменяемой ссылке).
Объект неизменяем: если однажды он был создан, он всегда представляет одно и то же значение (не имеет метода, изменяющего значение).
Почему
String
неизменный?
Соблюдайте приведенное выше определение, которое можно проверить, просмотрев исходный код Sting.java.
Каковы преимущества / недостатки неизменяемых объектов? неизменяемые типы:
безопаснее от ошибок.
легче понять.
и многое другое готово к переменам.
Почему изменяемый объект, такой как StringBuilder, должен быть предпочтительнее String и наоборот?
Сужая вопрос: зачем нам в программировании изменяемый StringBuilder? Обычно его используют для объединения большого количества строк, например:
String s = "";
for (int i = 0; i < n; ++i) {
s = s + n;
}
Используя неизменяемые строки, создается множество временных копий - первое число строки ("0") фактически копируется n раз в процессе построения последней строки, второе число копируется n-1 раз, и поэтому на. На самом деле просто копирование стоит O(n2) времени, хотя мы объединили только n элементов.
StringBuilder разработан для минимизации этого копирования. Он использует простую, но умную внутреннюю структуру данных, чтобы избежать копирования до самого конца, когда вы запрашиваете последнюю строку с помощью вызова toString():
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; ++i) {
sb.append(String.valueOf(n));
}
String s = sb.toString();
Хорошая производительность - одна из причин, почему мы используем изменяемые объекты. Другой - удобный совместный доступ: две части вашей программы могут общаться более удобно, разделяя общую изменяемую структуру данных.
Больше можно найти здесь: https://web.mit.edu/6.005/www/fa15/classes/09-immutability/
Неизменные объекты
Объект считается неизменным, если его состояние не может измениться после его создания. Максимальная опора на неизменяемые объекты широко признана в качестве разумной стратегии для создания простого и надежного кода.
Неизменяемые объекты особенно полезны в параллельных приложениях. Так как они не могут изменить состояние, они не могут быть повреждены из-за помех потока или наблюдаются в несовместимом состоянии.
Программисты часто неохотно используют неизменные объекты, потому что они беспокоятся о стоимости создания нового объекта, а не об обновлении объекта на месте. Воздействие создания объекта часто переоценивается и может быть компенсировано некоторыми из преимуществ, связанных с неизменяемыми объектами. К ним относятся снижение накладных расходов из-за сбора мусора и устранение кода, необходимого для защиты изменяемых объектов от повреждения.
Следующие подразделы берут класс, экземпляры которого являются изменяемыми, и получают класс с неизменяемыми экземплярами из него. При этом они дают общие правила для такого рода преобразования и демонстрируют некоторые преимущества неизменных объектов.
Объект считается неизменным, если его состояние не может измениться после его создания. Максимальная опора на неизменяемые объекты широко признана в качестве разумной стратегии для создания простого и надежного кода.
Неизменяемые объекты особенно полезны в параллельных приложениях. Так как они не могут изменить состояние, они не могут быть повреждены из-за помех потока или наблюдаются в несовместимом состоянии.
Мне нравится эта фраза из поста
Неизменяемые объекты облегчают параллельное программирование
Неизменяемый объект - это объект, который нельзя изменить после его создания. Типичным примером являются строковые литералы.
Язык программирования A D, который становится все более популярным, имеет понятие "неизменяемость" через ключевое слово "инвариант". Ознакомьтесь с этой статьей Dr.Dobb об этом - http://dobbscodetalk.com/index.php?option=com_myblog&show=Invariant-Strings.html&Itemid=29. Это прекрасно объясняет проблему.