Почему следующий пример опровергает то, что строки являются неизменяемыми объектами в Java?
Я использую Java-компилятор OpenJDK под Ubuntu. Я хотел преобразовать массив символов в строку, и когда это, казалось, привело к неоднозначным результатам, я попытался написать toString
мой метод. В процессе я написал тестовую программу, в которой (без удовольствия) попытался скомпилировать следующий код.
class toString{
public static void main(String[] args){
string = "abc";
string = string + "bcd";
System.out.println(string);
}
}
Теперь я знаю, что String
объекты в Java являются неизменяемыми, и код должен был фактически сгенерировать ошибку, но, к моему удивлению, он напечатал abcbcd
на консоль. Значит ли это, что String
объекты в Java изменчивы или в этом случае что-то не так с реализацией компилятора OpenJDK?
9 ответов
Разница между ссылками на объект и самим объектом.
String XXX = "xxx";
Средства. Создайте новую переменную и назначьте ссылку на экземпляр объекта String, который содержит буквенную строку "xxx".
XXX = XXX + "yyy";
Средства:
Получить ссылку на объект, который мы имеем в переменной с именем XXX. Создайте новый объект типа String, который содержит строковый литерал "yyy". Добавьте их вместе, выполнив строку + оператор. Эта операция создаст новый объект String, содержащий буквальную строку "xxxyyy". После всего этого мы снова помещаем ссылку на новый объект в переменную XXX.
Старый ссылочный объект, содержащий "xxx", больше не используется, но его содержимое никогда не изменялось.
В качестве встречного доказательства есть пример:
String a = "abc";
String b = "def";
String c = a;
a = a + b;
System.out.println(a); // will print "abcdef".
System.out.println(b); // will print "def".
System.out.println(c); // will print "abc".
// Now we compare references, in java == operator compare references, not the content of objects.
System.out.println(a == a); // Will print true
System.out.println(a == c); // Will print false, objects are not the same!
a = c;
System.out.println(a == c); // Will print true, now a and b points on the same instance.
Экземпляр объекта - это нечто "абстрактное", которое живет в части памяти вашей программы. Ссылочная переменная - это ссылка на эту часть памяти. Вы можете получить доступ к объектам только через переменные (или возвращаемые значения).
Один объект может иметь более одной переменной, указывающей на него.
String является неизменным, это означает, что вы не можете изменять содержимое этой области памяти, используемой объектом String, но, конечно, вы можете изменять и обмениваться ссылками на него по своему усмотрению.
Код, который вы разместили выше, на самом деле не изменяет строки, хотя выглядит так. Причина в том, что эта строка не изменяет строку:
string = string + "bcd";
Вместо этого, что это делает:
- Создайте новую строку, значение которой
string + "bcd"
, - Изменить на какую строку ссылается
string
ссылаться на эту новую строку.
Другими словами, сами конкретные конкретные строковые объекты не были изменены, но ссылки на эти строки действительно были изменены. Неизменность в Java обычно означает, что объекты не могут быть изменены, а не ссылки на эти объекты.
Важной деталью, которая сбивает с толку многих новых Java-программистов, является то, что приведенная выше строка часто записывается как
string += "bcd";
который выглядит еще сильнее, как будто он объединяет bcd
на конец строки и, таким образом, изменяя его, даже если он эквивалентен приведенному выше коду и, следовательно, не вызывает никаких изменений в фактическом String
объект (опять же, он работает, создавая новый String
объект и изменение объекта, на который ссылается ссылка.)
Чтобы увидеть, что здесь происходит то, что вы на самом деле меняете ссылку, а не строку, на которую она ссылается, вы можете попробовать переписать код, чтобы сделать string
final
, который не позволяет вам изменить, на какой объект ссылаются. Если вы сделаете это, вы обнаружите, что код больше не компилируется. Например:
class toString{
public static void main(String[] args){
final String string = "abc";
string = string + "bcd"; // Error: can't change string!
System.out.println(string);
}
}
Последнее замечание - еще одна распространенная причина скорби для новых Java-программистов при использовании String
это то, что String
есть методы, которые видоизменяют строку, но на самом деле нет. Например, этот код не работает правильно:
String s = "HELLO, WORLD!";
s.toLowerCase(); // Legal but incorrect
System.out.println(s); // Prints HELLO, WORLD!
Здесь призыв к s.toLowerCase()
фактически не преобразует символы строки в нижний регистр, а вместо этого создает новую строку с символами, установленными в нижний регистр. Если вы затем перепишите код как
String s = "HELLO, WORLD!";
s = s.toLowerCase(); // Legal and correct
System.out.println(s); // Prints hello, world!
Тогда код будет вести себя правильно. Опять же, ключевая деталь здесь заключается в том, что назначение s
не меняет бетон String
объект, но просто настраивает, какой объект s
относится к.
Надеюсь это поможет!
Нет, нет ошибки - вы не изменяете содержимое любого строкового объекта.
Вы изменяете значение строковой переменной, которая совершенно другая. Посмотрите на это как на две операции:
- Создание новой строки, результат выражения
string + "bcd"
- Присваивание ссылки на новую строку обратно
string
переменная
Давайте выделим их явно:
String string = "abc";
String other = string + "bcd";
// abc - neither the value of string nor the object's contents have changed
System.out.println(string);
// This is *just* changing the value of the string variable. It's not making
// any changes to the data within any objects.
string = other;
Очень важно различать переменные и объекты. Значение переменной - это всегда только ссылка или значение примитивного типа. Изменение значения переменной не меняет содержимого объекта, на который она ранее ссылалась.
Это не опровергает это. На самом деле он не скомпилируется, так как string не объявлен как объект String. Но, допустим, вы имели в виду:
class toString{
public static void main(String[] args){
String string = "abc";
string = string + "bcd";
System.out.println(string);
}
}
Посмотрите + оператор создает новую строку, оставляя "abc" в такте. Оригинальный "abc" все еще существует, но все, что вы на самом деле сделали, - это создайте новую строку "abcbcd" и перезапишите исходную ссылку на "abc" при выполнении: string = string + "bcd". Если вы изменили этот код на этот, вы поймете, что я имею в виду:
class toString {
public static void main(String[] args ) {
String originalString = "abc";
String newString = originalString + "bcd";
System.out.println( originalString ); // prints the original "abc";
System.out.println( newString ); // prints the new string "abcbcd";
}
}
string = string + "bcd"
устанавливает новый экземпляр String
к переменной string
, а не изменять этот объект
Строка является неизменной... все, что вы делаете в этом примере, показывает, что вы можете назначить новую строковую ссылку на переменную.
Если мы сделаем код скомпилированным и слегка его изменим:
class toString{
public static void main(String[] args){
String string = "abc";
System.out.println(string);
string = string + "bcd";
System.out.println(string);
}
}
Вы увидите "abc", а затем "abcbcd", что может заставить вас думать, что строка изменилась, но это не так.
Когда вы делаете строку = /* что угодно */, вы перезаписываете то, что раньше было в переменной, называемой строкой, с новым значением.
Если бы у String был метод, такой как setCharAt(int index, char value), то он был бы изменчивым.
String
объекты неизменны, вы просто переназначаете значение string
в string + "bcd"
в вашем примере кода. Вы не изменяете существующий String
объект, вы создаете новый и назначаете его старому имени.
Что он будет делать, так это то, что он создаст новый объект и заменит старый. если вы хотите непостоянную строку, посмотрите на строителя строк.
String
переменная string
сам изменчив.
String
объект "abc"
является неизменным, как и объект String "bcd"
и результат объединения, "abcbcd"
, Этот последний результат присваивается переменной.
Строка не была видоизменена при выполнении этого кода.