Чому це не створює NullPointerException?


89

Пояснення Nead для наступного коду:

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тому що те, що я бачу і розумію з перших двох прикладів, це те, що два об’єкти, що посилаються на один і той же об’єкт, тоді, якщо ми змінимо будь-яке значення, це також відобразиться на іншому, оскільки обидва вказують на те саме посилання на пам'ять. То чому це правило тут не застосовується? Якщо я призначаю nullreferenceToSample, тоді зразок також повинен бути нульовим, і він повинен кидати 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, якщо це відповідає на ваше запитання, клацніть галочку під стрілками для голосування ліворуч від тексту вище.
Ray Britton

@RayBritton Мені подобається, як люди припускають, що найголосніша відповідь - це та, яка обов’язково відповідає на питання. Хоча цей підрахунок важливий, він не єдина метрика; Відповіді -1 та -2 можуть бути прийняті лише тому, що вони найбільше допомогли ОП.
nanofarad

11
hashCode()це не те саме, що адреса пам'яті
Кевін Панько

6
Ідентифікаційний hashCode, як правило, отримується з розташування пам’яті Об’єкта під час першого виклику методу. Відтепер цей hashCode фіксується і запам'ятовується, навіть якщо менеджер пам'яті вирішує перемістити об'єкт в інше місце пам'яті.
Holger

3
Класи, які замінюють, hashCodeзазвичай визначають його для повернення значення, яке не має нічого спільного з адресами пам'яті. Я думаю, ви можете висловити свою думку щодо посилань на об’єкти та об’єктів, не згадуючи hashCode. Детальніші моменти, як отримати адресу пам'яті, можна залишити на інший день.
Kevin Panko

62

Спочатку все було так, як ви сказали, referToSampleмаючи на увазі, sampleяк показано нижче:

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

referToSample відноситься до зразка

2. Сценарій 1 (продовження):

referToSample.append ("B")

  • Тут як referToSampleмалося на увазі sample, так він додав "Б", поки ви пишете

    referToSample.append("B")

Те саме трапилось у сценарії 2:

Але, у 3. Сценарій3: як сказав гексафракція,

при призначенні nullна , referToSampleколи він мав в виду sampleйого не змінити значення замість цього він просто розриває посилання з sample, і тепер він не вказує ніде . як показано нижче:

коли referToSample = null

Тепер, як ніде неreferToSample вказує , тому, поки ви не мали б жодних значень або посилань, де б він міг додати А. Отже, це б кинулоreferToSample.append("A");NullPointerException .

АЛЕ sampleвсе ще те саме, що ви його ініціалізували

StringBuilder sample = new StringBuilder(); отже, він був ініціалізований, тож тепер він може додати А, а не кидатиме NullPointerException


5
Гарні схеми. Допомагає це проілюструвати.
нанофарад

приємна схема, але примітка внизу повинна бути "Now referToSample не відноситься до" "', тому що коли ви referToSample = sample;посилаєтесь на зразок, ви просто копіюєте адресу того, на що посилається
Khaled.K

17

У двох словах: Ви присвоюєте null посилальній змінній, а не об'єкту.

В одному прикладі ви змінюєте стан об'єкта, на який посилаються дві посилальні змінні. Коли це станеться, обидві посилальні змінні відображатимуть зміну.

В іншому прикладі ви змінюєте посилання, призначене одній змінній, але це не впливає на сам об'єкт, і тому друга змінна, яка все ще посилається на вихідний об'єкт, не помітить жодної зміни стану об'єкта.


Щодо ваших конкретних "правил":

якщо два об'єкти, що посилаються на один і той самий об'єкт, тоді, якщо ми змінимо будь-яке значення, воно також відобразиться на іншому, оскільки обидва вказують на одне посилання на пам'ять.

Знову ж ви маєте на увазі зміну стану одного об’єкта, на який посилаються обидві змінні .

То чому тут це правило не застосовується? Якщо я присвоюю null для referenceToSample, тоді зразок також повинен бути нульовим, і він повинен кидати nullPointerException, але це не кидає, чому?

Знову ж таки, ви змінюєте посилання на одну змінну, що абсолютно не впливає на посилання на іншу змінну.

Це дві абсолютно різні дії, що призведе до двох абсолютно різних результатів.


3
@commit: це основна, але критична концепція, яка лежить в основі всієї Java, і яку, побачивши, ви ніколи не забудете.
Судно на повітряній подушці, повне вугрів,

8

Дивіться цю просту схему:

діаграма

При виклику методу на referToSample, потім [your object]оновлюється, так що це впливає sampleзанадто. Але коли ви говорите referToSample = null, тоді ви просто змінюєте те, що referToSample стосується .


3

Тут 'sample' і 'referToSample' посилаються на один і той же об'єкт. Це концепція різного вказівника, що отримує доступ до одного і того ж місця пам'яті. Отже, присвоєння однієї посилальної змінної null не знищує об’єкт.

   referToSample = null;

означає 'referToSample', лише вказуючи на нуль, об'єкт залишається незмінним, а інші посилальні змінні працюють нормально. Отже, для "зразка", який не вказує на нуль і має дійсний об'єкт

   sample.append("A");

працює нормально. Але якщо ми спробуємо додати null до 'referToSample', він покаже NullPointException. Це є,

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

Ось чому ви отримали NullPointerException у своєму третьому фрагменті коду.


0

Щоразу, коли використовується нове ключове слово, воно створює об’єкт у купі

1) StringBuilder зразок = new StringBuilder ();

2) StringBuilder referToSample = зразок;

У 2) Посилання referenceSample створюється на тому ж зразку об'єкта

таким чином referToSample = null; є нульовим Тільки посилання referSample, що не дає ефекту для зразка, тому ви не отримуєте виняток NULL Pointer завдяки колекції сміття Java


0

Просто, Java не має передавати посилання, вона просто передає посилання на об’єкт.


2
Семантика передачі параметрів насправді не має нічого спільного із ситуацією, як описано.
Майкл Майєрс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.