19

Добрый день. Читал про вызов по значению и натолкнулся на два примера. 1-ый:

public class Application {
    public static void main(String[] args) {
        String[] x = {"A"};
        String[] y = x;
        x[0] = "B";
        System.out.print(x[0] + " " + y[0]);
    }
}

Тут вроде бы все понятно. Ссылка на строковый массив "y" будет совпадать со ссылкой на массив "x"и произойдет копирование и ответом будет "B B". Но вот второй пример:

public class Application {
    public static void main(String[] args) {
        String x = "A";
        String y = x;
        x = "B";
        System.out.print(x + " " + y);
    }
}

Здесь почему то ответ "B A", хотя я думал что правильный ответ будет "B B". Ход рассуждений был такой же как и в первом примере. Подскажите пожалуйста, что за подвох в этом примере ? Ведь String - это класс, а не тип в Java и в обоих примерах мы создаем экземпляр класса String.

Grundy
  • 81,538
Drylozav
  • 521

3 Answers3

20

В первом случае у вас есть массив (обозначим его M, хотя в Java вы не можете обратиться к нему иначе, как через ссылку). Нулевой элемент массива M указывает сначала на строку "A", затем на строку "B". И x, и y, заметьте, ссылаются на один и тот же массив M. Когда вы меняете данные в массиве через любую из ссылок, вы меняете одни и те же данные.

Во втором случае у нас есть переменные x и y, которые ссылаются сначала на одну и ту же строку "A", а затем x начинает ссылаться на другую строку ("B"). Всё просто.


Вот вам аналогия. В первом случае у вас есть клетка. И вы, и я смотрим на эту клетку. В клетке сначала сидел белый кролик, а потом туда посадили чёрного кролика. Теперь и вы, и я смотрим на клетку с чёрным кроликом.

Во втором случае клетки нет, я смотрю на белого кролика, вы смотрите на того же кролика, что и я, а потом я отвернулся и стал смотреть на чёрного кролика. Ну да, теперь мы смотрим на разных кроликов.

Теперь замените кроликов на строки, клетку на массив, «смотреть» на «иметь ссылку».


Вы не должны искать универсальных простых правил типа «если я меняю вот эту штуку, та тоже поменяется». Или «не поменяется». Или «поменяется, если уровень косвенности больше 1». Рассуждайте о смысле операций каждый раз.


Не удержался, добавил картинки, о которых говорил @void.

Смотрите. Первый пример:

String[] x = {"A"};

String[] x = {"A"};

String[] y = x;

String[] y = x;

x[0] = "B";

x[0] = "B";

Второй пример:

String x = "A";

String x = "A";

String y = x;

String y = x;

x = "B";

x = "B";

Так понятнее?

VladD
  • 206,799
  • я не могу понять в чем различие , ведь строчка String y = x; присутствует в обоих примерах. Тем более String - это ссылочный тип. Значит ссылка (в обоих примерах), указывает на то, что данные одного массива будут копироваться в другой. Под "В первом случае у вас есть массив (обозначим его M)" вы имеете в виду пул строк ? – Drylozav Oct 30 '13 at 17:17
  • в первом примере вы изменили объект, а кто вам сказал что во втором примере вы изменили объект У ?? вы просто изменили ссылку У. объект то вы не трогали. – arg Oct 30 '13 at 17:22
  • 1
    Пул строк тут не причём. Вы знаете, что означает String[]? Это не String. Прочитайте лучше аналогию про кроликов.

    И ещё раз, не думайте, что раз и там и там написано «y = x», то и вести себя будет одинаково. Думайте о смысле.

    – VladD Oct 30 '13 at 17:23
  • Простите а чем отличается String[] от String ? Я имею в виду, что String - это объектный тип и создав его экземпляр, мы можем указывать на него. – Drylozav Oct 30 '13 at 17:32
  • 1
    Это разные типы. В первом случае вы меняете не всю клетку, а лишь кролика в клетке. Смотрите внимательно на присваивание x[0] = "B". – VladD Oct 30 '13 at 17:37
  • Я понимаю вашу аналогию. Просто я немного не понял насчет String. Получается, что String x = "A" - это просто строковый литерал , переменная, а String[] x = {"A"}; - это уже объект , экземпляр класса String. Извините, просто что то не могу понять и все. Хотя, повторюсь ваша аналогия понятна – Drylozav Oct 30 '13 at 17:47
  • @Drylozav String x = "A" - x указывает на объект типа String. В свою очередь String[] x = {"A"} - x указывает на объект типа массив строк, который состоит из одного элемента типа String с индексом 0. – a_gura Oct 30 '13 at 17:53
  • @Drylozav: String x = "A" присваивает x ссылку на строку, содержащую текст A. Строка является объектом.

    String[] x = {"A"} присваивает x ссылку на массив строк, содержащий одну ссылку на строку, которая содержит текст A. Массив строк тоже является объектом. x[0] есть эта самая ссылка на строку.

    – VladD Oct 30 '13 at 18:58
  • 1
    @Vlad - замечательно, плюсую! @Drylozav - не сочтите за иронию, не думайте, что Java ведет себя неправильно. Я, точнее выражаясь, думаю, что и вы знаете, - я более чем уверен в этом, - что Java не ведет себя как-то не так, поэтому я всего лишь хочу сказать, что вы на правильном пути. Достаньте лист бумаги и начертите то, о чем вам сказал @VladD. Схематично представьте себе. Очертите прямоугольниками строки А и В. Начертите массив, элемент которого указывает на строку А. {.пауза.} А затем графически проанализируйте. Коммент не позволяет писать больше. P.S. Плюс за вопрос - зачитался сам – void Oct 30 '13 at 21:44
  • String - иммутабельный объект – Sublihim Jan 16 '17 at 09:35
  • @Sublihim: Да, а что? – VladD Jan 16 '17 at 09:45
  • @VladD, видимо комментарий не туда приписал. Поэтому он и печатает "B A" :) – Sublihim Jan 16 '17 at 09:48
  • @Sublihim: А, понял :) – VladD Jan 16 '17 at 09:50
  • Думаю, еще понятнее будет, если объяснить, что в строке x = "B"; просто создается новый объект "строка" и сссылка на него присваивается переменной х. Типа x = new String('B'); –  Jan 20 '18 at 14:10
  • @ОлексійМоренець: Если я не ошибаюсь, там немного не так, новая строка не создаётся, а берётся одна общая. – VladD Jan 20 '18 at 14:55
  • @VladD, это уже другая история. Но даже если бы строка "В" уже существовала, то х и у все равно указывали бы на разные области памяти. –  Jan 20 '18 at 16:32
6

В первом случае у вас есть 2 ссылки на один и тот же массив.

String[] x = {"A"}; //x указывает на массив {"A"} (x[0] == "A")
String[] y = x; //x и y указывают на массив {"A"} (x == y; x[0] == y[0]; x[0] == "A"; y[0] == "A")
x[0] = "B"; //в массиве {"A"} строка с индекcом 0 поменялась на строку "B", изменилось состояние массива и теперь это массив {"B"}. Но это все еще тот же самый объект (x == y; x[0] == y[0]; x[0] == "B"; y[0] == "B")

Во втором же случае у вас две разных ссылки на экземпляры класса String:

String x = "A"; //x указывает на строку "A"
String y = x; //x и y указывают на строку "A" (x == y)
String x = "B" //x указывает на строку "B", y указывает на строку "A" (x != y)
a_gura
  • 13,169
  • Но какая разница ? ведь в первом примере тоже есть две ссылки на экземпляр класса String, которые представлены строковым массивом и как вы сказали есть 2 ссылки, которые указывают на один и тот же массив. В этом примере ведь тоже самое : две ссылки. – Drylozav Oct 30 '13 at 17:24
  • в первом примере НЕТ ссылки на экземляр String. я её в упор не вижу. там есть ссылка на МАССИВ который типизирован String'ом. – arg Oct 30 '13 at 17:33
  • как нет ? ведь String - это класс, любой экземпляр которого порождает объект – Drylozav Oct 30 '13 at 17:37
  • попробуй замени String x = "A" на String x = new String("A") – Севак Аветисян Oct 30 '13 at 17:46
  • @Dryzolav я дополнил ответ. Надеюсь, стало понятнее. – a_gura Oct 30 '13 at 17:51
0

Никак невозможно изменить содержимое созданной строки, по крайней мере в безопасном (safe) коде и без рефлексии. Поэтому вы при изменении строк изменяете не сами строки, а значения переменных, указывающих на строки. Например, код s = s.Replace ("foo", "bar"); не изменяет содержимое строки s, которое было до вызова метода Replace — он просто переназначает переменную s на новообразованную строку, которая является копией старой за исключением всех подстрок «foo», заменённых на «bar».

vad
  • 1
  • 1
    Не относится к вопросу. Ситуация с изменяемым объектом была бы такой же в случае присваивания. – Qwertiy Jan 16 '17 at 09:44