Чому це не створює 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.