Почему при этом не генерируется исключение NullPointerException?

89

Необходимое разъяснение для следующего кода:

StringBuilder sample = new StringBuilder();
StringBuilder referToSample = sample;
referToSample.append("B");
System.out.println(sample);

Это будет напечатано, Bчтобы доказательства sampleи referToSampleобъекты ссылались на одну и ту же ссылку в памяти.

StringBuilder sample = new StringBuilder();
StringBuilder referToSample = sample;
sample.append("A");
referToSample.append("B");
System.out.println(referToSample);

Это напечатает, ABчто тоже доказывает то же самое.

StringBuilder sample = new StringBuilder();
StringBuilder referToSample = sample;
referToSample = null;
referToSample.append("A");
System.out.println(sample);

Очевидно, это вызовет ошибку, NullPointerExceptionпотому что я пытаюсь вызвать appendнулевую ссылку.

StringBuilder sample = new StringBuilder();
StringBuilder referToSample = sample;
referToSample = null;
sample.append("A");
System.out.println(sample);

Итак, вот мой вопрос, почему последний образец кода не бросается, NullPointerExceptionпотому что то, что я вижу и понимаю из первых двух примеров, - это если два объекта относятся к одному и тому же объекту, тогда, если мы изменим какое-либо значение, оно также отразится на другом, потому что оба указывают на та же ссылка на память. Так почему это правило здесь не применяется? Если я назначаю nullreferToSample, тогда образец также должен быть нулевым, и он должен генерировать исключение NullPointerException, но не выбрасывает его, почему?

совершить
источник
31
sampleвсе еще sample. Вы только изменились referToSample.
Дэйв Ньютон
25
Проголосовали / помечены! Очень простой вопрос, но это прекрасный пример объяснения вашей проблемы и правильной постановки вопроса.
Райан Рэнсфорд
15
Один пункт терминологии в вашем вопросе: вы продолжаете ссылаться на объектыsample и referToSampleкак на объекты, но это не объекты, это переменные. Переменная может содержать ссылку на объект, но сама по себе не является объектом. Это тонкое различие, но в основном это суть вашего замешательства.
Дэниел Прайден
1
Это помогает рассматривать объектные переменные как просто указатели. Любой оператор , который действует на переменном ( volatile, final, =, ==...) , когда применяются к переменному влияет на указатель , а не объект , он ссылается.
MikeFHay
2
@Arpit Я с уважением не согласен. В этом вопросе скрыта большая концепция, а именно разница между объектом и ссылкой на этот объект. Большую часть времени, нам не нужно ( и не хотят) , чтобы быть в курсе этой разницы, и разработчики языка упорно трудиться , чтобы скрыть его от нас. Подумайте, например, об аргументах передачи по ссылке в C ++. Поэтому для меня не удивительно видеть новичков, сбитых с толку всей этой магией!
Gyom

Ответы:

89

nullназначения не изменяют значение , глобально уничтожая этот объект. Такое поведение привело бы к трудно отслеживаемым ошибкам и противоречивому поведению. Они нарушают только эту конкретную ссылку .

Для простоты предположим, что это sampleуказывает на адрес 12345. Вероятно, это не адрес, и он используется здесь только для упрощения работы. Адрес обычно представляется в странном шестнадцатеричном формате Object#hashCode(), но это зависит от реализации. 1

StringBuilder sample = new StringBuilder(); //sample refers to 
//StringBuilder at 12345 

StringBuilder referToSample = sample; //referToSample refers to 
//the same StringBuilder at 12345 
//SEE DIAGRAM 1

referToSample = null; //referToSample NOW refers to 00000, 
//so accessing it will throw a NPE. 
//The other reference is not affected.
//SEE DIAGRAM 2

sample.append("A"); //sample STILL refers to the same StringBuilder at 12345 
System.out.println(sample);

Из отмеченных линий See diagramсхемы объектов на тот момент выглядят следующим образом:

Диаграмма 1:

[StringBuilder sample]    -----------------> [java.lang.StringBuilder@00012345]
                                                      ↑
[StringBuilder referToSample] ------------------------/

Диаграмма 2:

[StringBuilder sample]    -----------------> [java.lang.StringBuilder@00012345]

[StringBuilder referToSample] ---->> [null pointer]

На диаграмме 2 показано, что аннулирование referToSampleне нарушает ссылку sampleна StringBuilder в 00012345.

1 Соображения GC делают это неправдоподобным.

нанофарад
источник
@commit, если это ответ на ваш вопрос, нажмите на галочку под стрелками голосования слева от текста выше.
Рэй Бриттон
@RayBritton Мне нравится, как люди предполагают, что ответ, получивший наибольшее количество голосов, обязательно отвечает на вопрос. Хотя это количество важно, это не единственный показатель; Ответы -1 и -2 могут быть приняты только потому, что они больше всего помогли ОП.
нанофарад 05
11
hashCode()- это не то же самое, что адрес памяти
Кевин Панко
6
Идентификационный hashCode обычно получается из области памяти объекта при первом вызове метода. С этого момента этот hashCode фиксируется и запоминается, даже если диспетчер памяти решает переместить объект в другое место в памяти.
Хольгер
3
Классы, которые переопределяют, hashCodeобычно определяют его так, чтобы он возвращал значение, не имеющее ничего общего с адресами памяти. Я думаю, вы можете выразить свою точку зрения относительно ссылок на объекты и объектов без упоминания hashCode. Более тонкие моменты получения адреса памяти можно отложить на другой день.
Кевин Панко
62

Первоначально, как вы сказали, referToSampleимелось в виду, sampleкак показано ниже:

1. Сценарий 1:

referToSample относится к образцу

2. Сценарий 1 (продолжение):

referToSample.append ("B")

  • Здесь, как referToSampleимелось в виду sample, добавляется буква "B", пока вы пишете

    referToSample.append("B")

То же самое произошло в сценарии 2:

Но в 3. Сценарий 3: как сказано в гексафракции,

при назначении nullна , referToSampleкогда он имел в виду sampleего не изменить значение вместо этого он просто разрывает ссылку из sample, и теперь он не указывает нигде . как показано ниже:

когда referToSample = null

Теперь, как referToSampleточки нигде , так что пока вы referToSample.append("A");это не будете иметь любые значения или ссылки , где он может приобщать А. Таким образом, было бы бросить NullPointerException.

НО sampleостается таким же, как и вы инициализировали его с помощью

StringBuilder sample = new StringBuilder(); поэтому он был инициализирован, поэтому теперь он может добавлять A и не будет бросать NullPointerException

Парт
источник
5
Хорошие диаграммы. Помогает это проиллюстрировать.
нанофарад 06
хорошая диаграмма, но примечание внизу должно быть «Теперь referToSample не ссылается на« »», потому что когда вы делаете это, referToSample = sample;вы ссылаетесь на образец, вы просто копируете адрес того, на что ссылаетесь
Khaled.K
17

В двух словах: вы присваиваете null ссылочной переменной, а не объекту.

В одном примере вы изменяете состояние объекта, на который ссылаются две ссылочные переменные. Когда это произойдет, обе ссылочные переменные будут отражать изменение.

В другом примере вы меняете ссылку, назначенную одной переменной, но это не влияет на сам объект, и поэтому вторая переменная, которая все еще ссылается на исходный объект, не заметит никаких изменений в состоянии объекта.


Итак, что касается ваших конкретных «правил»:

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

Опять же, вы имеете в виду изменение состояния одного объекта, на который ссылаются обе переменные .

Так почему это правило здесь не применяется? Если я присваиваю null для referToSample, тогда образец также должен иметь значение null, и он должен генерировать nullPointerException, но он не бросает, почему?

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

Это два совершенно разных действия, которые приведут к двум совершенно разным результатам.

Судно на воздушной подушке, полное угрей
источник
3
@commit: это базовая, но важная концепция, лежащая в основе всей Java, и которую вы никогда не забудете, увидев ее.
Судно на воздушной подушке Full Of Eels
8

Смотрите эту простую диаграмму:

диаграмма

При вызове метода на referToSample, затем [your object]обновляется, так что это влияет sampleслишком. Но когда вы говорите referToSample = null, вы просто меняете то, к чему referToSample относится .

tckmn
источник
3

Здесь 'sample' и 'referToSample' ссылаются на один и тот же объект. Это концепция разных указателей, обращающихся к одной и той же области памяти. Таким образом, присвоение одной ссылочной переменной null не уничтожает объект.

   referToSample = null;

означает, что «referToSample» указывает только на null, объект остается таким же, а другая ссылочная переменная работает нормально. Итак, для 'образца', который не указывает на нуль и имеет действительный объект

   sample.append("A");

работает отлично. Но если мы попытаемся добавить null к «referToSample», он покажет исключение NullPointException. То есть,

   referToSample .append("A");-------> NullPointerException

Вот почему в третьем фрагменте кода есть исключение NullPointerException.

NCA
источник
0

Всякий раз, когда используется новое ключевое слово, оно создает объект в куче

1) Пример StringBuilder = новый StringBuilder ();

2) StringBuilder referToSample = sample;

Во 2) Ссылка на referSample создается на том же образце объекта

таким образом, referToSample = null; Обнуляет Только ссылка referSample, не влияющая на образец, поэтому вы не получаете исключение указателя NULL благодаря сборке мусора Java

Нитеш
источник
0

Просто в Java нет передачи по ссылке, она просто передает ссылку на объект.

Peerapat A
источник
2
Семантика передачи параметров на самом деле не имеет ничего общего с описанной ситуацией.
Майкл Майерс