Строка неизменяема. Что именно это означает?
Я написал следующий код для неизменяемых строк.
public class ImmutableStrings {
public static void main(String[] args) {
testmethod();
}
private static void testmethod() {
String a = "a";
System.out.println("a 1-->" + a);
a = "ty";
System.out.println("a 2-->" + a);
}
}
Вывод:
a 1-->a
a 2-->ty
Здесь значение переменной a
было изменено (хотя многие говорят, что содержимое неизменяемых объектов изменить нельзя). Но что именно подразумевается, говоря, что String
является неизменяемым? Не могли бы вы, пожалуйста, прояснить для меня эту тему?
источник : https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html
Переведено автоматически
Ответ 1
Прежде чем продолжить обсуждение неизменяемости, давайте просто немного взглянем на String
класс и его функциональность, прежде чем прийти к какому-либо выводу.
Вот как String
работает:
String str = "knowledge";
Это, как обычно, создает строку, содержащую "knowledge"
и присваивает ей ссылку str
. Достаточно просто? Давайте выполним еще несколько функций:
String s = str; // assigns a new reference to the same string "knowledge"
Давайте посмотрим, как работает приведенный ниже оператор:
str = str.concat(" base");
Это добавляет строку " base"
к str
. Но подождите, как это возможно, поскольку String
объекты неизменяемы? Что ж, к вашему удивлению, это так.
Когда выполняется приведенный выше оператор, виртуальная машина принимает значение String str
, т.Е. "knowledge"
и добавляет " base"
, давая нам значение "knowledge base"
. Теперь, поскольку String
s неизменяемы, виртуальная машина не может присвоить это значение str
, поэтому она создает новый String
объект, присваивает ему значение "knowledge base"
и дает на него ссылку str
.
Здесь важно отметить, что, хотя String
объект неизменяем, его ссылочная переменная - нет. Итак, вот почему в приведенном выше примере ссылка была сделана для обозначения вновь созданного String
объекта.
На данный момент в приведенном выше примере у нас есть два String
объекта: первый, который мы создали со значением "knowledge"
, на которое указывает s
, и второй, на который указывает "knowledge base"
str
. Но, технически, у нас есть три String
объекта, третий из которых является литералом "base"
в concat
инструкции.
Важные факты об использовании строки и памяти
Что, если бы у нас не было другой ссылки s
на "knowledge"
? Мы бы потеряли это String
. Тем не менее, она все равно существовала бы, но считалась бы потерянной из-за отсутствия ссылок. Посмотрите на еще один пример ниже
String s1 = "java";
s1.concat(" rules");
System.out.println("s1 refers to "+s1); // Yes, s1 still refers to "java"
Что происходит:
- Первая строка довольно проста: создайте новую
String
"java"
и обратитесьs1
к ней. - Затем виртуальная машина создает еще одну новую
String
"java rules"
, но на нее ничего не ссылается. Итак, втораяString
мгновенно теряется. Мы не можем до нее добраться.
Ссылочная переменная s1
по-прежнему ссылается на исходную String
"java"
.
Почти каждый метод, применяемый к String
объекту с целью его модификации, создает новый String
объект. Итак, куда же деваются эти String
объекты? Ну, они существуют в памяти, и одна из ключевых целей любого языка программирования - эффективно использовать память.
По мере роста приложений очень часто String
литералы занимают большую область памяти, что может даже привести к избыточности. Итак, чтобы сделать Java более эффективной, JVM выделяет специальную область памяти, называемую "Пулом строковых констант".
Когда компилятор видит String
литерал, он ищет String
в пуле. Если найдено совпадение, ссылка на новый литерал направляется на существующий String
и новый String
объект не создается. У существующего String
просто есть еще одна ссылка. Здесь возникает вопрос о том, как сделать String
объекты неизменяемыми:
В String
пуле констант у String
объекта, вероятно, будет одна или несколько ссылок. Если несколько ссылок указывают на одно и то же String
, даже не зная об этом, было бы плохо, если бы одна из ссылок изменила это String
значение. Вот почему String
объекты неизменяемы.
Ну, теперь вы могли бы сказать, что, если кто-то переопределит функциональность String
класса? Именно по этой причине String
класс помечен final
, чтобы никто не мог переопределить поведение его методов.
Ответ 2
Строка неизменяема означает, что вы не можете изменить сам объект, но вы можете изменить ссылку на объект.
При выполнении a = "ty"
вы фактически изменяете ссылку на a
на новый объект, созданный строковым литералом "ty"
.
Изменение объекта означает использование его методов для изменения одного из его полей (или поля являются общедоступными, а не окончательными, так что их можно обновлять извне, не обращаясь к ним с помощью методов), например:
Foo x = new Foo("the field");
x.setField("a new field");
System.out.println(x.getField()); // prints "a new field"
Находясь в неизменяемом классе (объявленном как final, чтобы предотвратить модификацию через наследование) (его методы не могут изменять его поля, а также поля всегда являются закрытыми и рекомендуется быть final ), например String , вы не можете изменить текущую строку, но вы можете вернуть новую строку, т.е:
String s = "some text";
s.substring(0,4);
System.out.println(s); // still printing "some text"
String a = s.substring(0,4);
System.out.println(a); // prints "some"
Ответ 3
Вы меняете то, на что a
ссылается. Попробуйте это:
String a="a";
System.out.println("a 1-->"+a);
String b=a;
a="ty";
System.out.println("a 2-->"+a);
System.out.println("b -->"+b);
Вы увидите, что объект, на который ссылается a
and then b
, не изменился.
Если вы хотите, чтобы ваш код не изменял, на какой объект a
ссылается, попробуйте:
final String a="a";
Ответ 4
Строка - это char[]
содержащая последовательность единиц кода UTF-16, int
смещение в этом массиве и int
длину.
Например.
String s
Это создает пространство для ссылки на строку. Присвоение копирует ссылки по кругу, но не изменяет объекты, на которые ссылаются эти ссылки.
Вы также должны знать, что
new String(s)
на самом деле ничего полезного не делает. Он просто создает другой экземпляр, поддерживаемый тем же массивом, смещением и длиной, что и s
. Для этого очень редко есть причина, поэтому большинство Java-программистов считают это плохой практикой.
Строки Java, заключенные в двойные кавычки, такие как "my string"
, на самом деле являются ссылками на интернированные String
экземпляры, поэтому "bar"
это ссылка на один и тот же экземпляр String, независимо от того, сколько раз он появляется в вашем коде.
"Hello" создает один экземпляр, который является объединенным, а new String(...)
создает экземпляр, не являющийся объединенным. Попробуйте System.out.println(("hello" == "hello") + "," + (new String("hello") == "hello") + "," + (new String("hello") == new String("hello")));
, и вы должны увидеть true,false,false