Почему следующий пример опровергает то, что строки являются неизменяемыми объектами в 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";

Вместо этого, что это делает:

  1. Создайте новую строку, значение которой string + "bcd",
  2. Изменить на какую строку ссылается string ссылаться на эту новую строку.

Другими словами, сами конкретные конкретные строковые объекты не были изменены, но ссылки на эти строки действительно были изменены. Неизменность в Java обычно означает, что объекты не могут быть изменены, а не ссылки на эти объекты.

Важной деталью, которая сбивает с толку многих новых Java-программистов, является то, что приведенная выше строка часто записывается как

string += "bcd";

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

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

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", Этот последний результат присваивается переменной.

Строка не была видоизменена при выполнении этого кода.

Другие вопросы по тегам