Mengapa ini tidak memunculkan NullPointerException?

89

Baca klarifikasi untuk kode berikut:

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

Ini akan mencetak Bsehingga bukti sampledan referToSampleobjek mengacu pada referensi memori yang sama.

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

Ini akan mencetak AByang juga membuktikan hal yang sama.

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

Jelas ini akan melempar NullPointerExceptionkarena saya mencoba memanggil appendreferensi nol.

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

Jadi inilah pertanyaan saya, mengapa sampel kode terakhir tidak dibuang NullPointerExceptionkarena yang saya lihat dan pahami dari dua contoh pertama adalah jika dua objek merujuk ke objek yang sama maka jika kita mengubah nilai apa pun maka itu juga akan mencerminkan yang lain karena keduanya menunjuk ke referensi memori yang sama. Jadi mengapa aturan itu tidak berlaku di sini? Jika saya menetapkan nullke referToSample maka sampel juga harus nol dan harus membuang NullPointerException tetapi tidak membuangnya, mengapa?

melakukan
sumber
31
samplemasih sample. Anda hanya berubah referToSample.
Dave Newton
25
Suara positif / bintang! Pertanyaan yang sangat mendasar, tetapi ini adalah contoh yang bagus untuk menjelaskan masalah Anda dan mengajukan pertanyaan dengan baik.
Ryan Ransford
15
Satu poin terminologi dalam pertanyaan Anda: Anda tetap merujuk ke sampledan referToSamplesebagai objek tetapi mereka bukan objek, mereka adalah variabel. Variabel dapat menyimpan referensi ke suatu objek, tetapi itu sendiri bukanlah objek. Ini perbedaan yang halus, tetapi pada dasarnya itu adalah inti dari kebingungan Anda.
Daniel Pryden
1
Ini membantu untuk memikirkan variabel objek hanya sebagai petunjuk. Setiap operator yang bekerja pada sebuah variabel ( volatile, final, =, ==...) bila diterapkan variabel objek mempengaruhi pointer , bukan objek itu mengacu.
MikeFHay
2
@Arpit Saya dengan hormat tidak setuju. Ada konsep besar yang tersembunyi dalam pertanyaan ini, dan itu adalah perbedaan antara sebuah objek dan referensi ke objek tersebut. Seringkali, kita tidak perlu (atau ingin) menyadari perbedaan ini, dan desainer bahasa bekerja keras untuk menyembunyikannya dari kita. Bayangkan saja argumen pass-by-reference dalam C ++, misalnya. Jadi tidak mengherankan bagi saya melihat pemula bingung dengan semua keajaiban itu!
Gyom

Jawaban:

89

nulltugas tidak mengubah nilai dengan menghancurkan objek itu secara global. Perilaku semacam itu akan menyebabkan bug yang sulit dilacak dan perilaku yang berlawanan dengan intuisi. Mereka hanya merusak referensi khusus itu .

Untuk kesederhanaan, katakanlah itu samplemenunjuk ke alamat 12345. Ini mungkin bukan alamatnya, dan hanya digunakan untuk mempermudah di sini. Alamat biasanya diwakili dengan heksadesimal aneh yang diberikan Object#hashCode(), tetapi ini bergantung pada implementasi. 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);

Dari garis-garis yang di tandai See diagramdiagram benda pada saat itu adalah sebagai berikut:

Diagram 1:

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

Diagram 2:

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

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

Diagram 2 menunjukkan bahwa pembatalan referToSampletidak memutuskan referensi dari sampleStringBuilder di 00012345.

Pertimbangan 1 GC membuat ini tidak masuk akal.

nanofarad.dll
sumber
@commit, jika ini menjawab pertanyaan Anda, silakan klik tanda centang di bawah panah suara di sebelah kiri teks di atas.
Ray Britton
@RayBritton Saya suka bagaimana orang berasumsi bahwa jawaban dengan suara terbanyak adalah jawaban yang perlu menjawab pertanyaan. Meskipun hitungan itu penting, ini bukan satu-satunya metrik; Jawaban -1 dan -2 bisa diterima hanya karena paling membantu OP.
nanofarad
11
hashCode()adalah tidak sama dengan alamat memori
Kevin Panko
6
Kode hash identitas biasanya berasal dari lokasi memori Objek saat pertama kali metode dipanggil. Sejak saat itu, kode hash tersebut diperbaiki dan diingat bahkan jika pengelola memori memutuskan untuk memindahkan objek ke lokasi memori yang berbeda.
Holger
3
Kelas yang menimpa hashCodebiasanya mendefinisikannya untuk mengembalikan nilai yang tidak ada hubungannya dengan alamat memori. Saya pikir Anda dapat membuat poin Anda tentang referensi objek vs. objek tanpa menyebutkan hashCode. Poin-poin penting tentang bagaimana mendapatkan alamat memori dapat ditinggalkan untuk hari lain.
Kevin Panko
62

Awalnya seperti yang Anda katakan referToSamplemerujuk sampleseperti yang ditunjukkan di bawah ini:

1. Skenario1:

referToSample mengacu pada sampel

2. Skenario1 (lanjutan):

referToSample.append ("B")

  • Di sini seperti referToSampleyang dimaksud sample, jadi itu ditambahkan "B" saat Anda menulis

    referToSample.append("B")

Hal yang sama terjadi di Scenario2:

Tapi, di 3. Scenario3: seperti kata hexafraction,

ketika Anda menetapkan nulluntuk referToSampleketika itu mengacu sampletidak perubahan nilai bukan hanya melanggar referensi dari sample, dan sekarang itu menunjuk tempat . seperti yang ditunjukkan di bawah ini:

ketika referToSample = null

Sekarang, sebagai tidakreferToSample menunjukkan kemana-mana , jadi sementara Anda referToSample.append("A");tidak akan memiliki nilai atau referensi di mana ia dapat menambahkan A. Jadi, itu akan dibuang NullPointerException.

TAPI samplemasih sama seperti saat Anda menginisialisasi

StringBuilder sample = new StringBuilder(); jadi sudah diinitalisasi, jadi sekarang bisa menambahkan A, dan tidak akan membuang NullPointerException

Parth
sumber
5
Diagram yang bagus. Membantu mengilustrasikannya.
nanofarad
diagram yang bagus, tetapi catatan di bagian bawah seharusnya adalah 'Sekarang referToSample tidak merujuk ke ""' karena ketika Anda melakukannya, referToSample = sample;Anda merujuk ke sampel, Anda hanya menyalin alamat dari apa yang direferensikan
Khaled.K
17

Singkatnya: Anda menetapkan null ke variabel referensi, bukan ke objek.

Dalam satu contoh, Anda mengubah status objek yang dirujuk oleh dua variabel referensi. Jika ini terjadi, kedua variabel referensi akan mencerminkan perubahan tersebut.

Dalam contoh lain, Anda mengubah referensi yang ditetapkan ke satu variabel, tetapi ini tidak berpengaruh pada objek itu sendiri, sehingga variabel kedua, yang masih merujuk ke objek asli, tidak akan melihat perubahan apa pun dalam status objek.


Untuk "aturan" spesifik Anda:

Jika dua objek merujuk ke objek yang sama maka jika kita mengubah nilai apapun maka itu juga akan mencerminkan yang lain karena keduanya menunjuk ke referensi memori yang sama.

Sekali lagi, Anda mengacu pada mengubah status satu objek yang dirujuk kedua variabel .

Jadi mengapa aturan itu tidak berlaku di sini? Jika saya menetapkan null ke referToSample maka sampel juga harus null dan harus membuang nullPointerException tetapi tidak membuang, mengapa?

Sekali lagi, Anda mengubah referensi satu variabel yang sama sekali tidak berpengaruh pada referensi variabel lainnya.

Ini adalah dua tindakan yang sangat berbeda dan akan menghasilkan dua hasil yang sangat berbeda.

Hovercraft Full Of Eels
sumber
3
@commit: ini adalah konsep dasar namun penting yang mendasari semua Java dan begitu Anda melihatnya, Anda tidak akan pernah lupa.
Hovercraft Full Of Eels
8

Lihat diagram sederhana ini:

diagram

Ketika Anda memanggil metode pada referToSample, kemudian [your object]diperbarui, sehingga mempengaruhi samplejuga. Tetapi ketika Anda mengatakannya referToSample = null, maka Anda hanya mengubah apa yang referToSample dimaksud .

tckmn
sumber
3

Di sini 'sample' dan 'referToSample' mereferensikan objek yang sama. Itu adalah konsep pointer berbeda yang mengakses lokasi memori yang sama. Jadi menugaskan satu variabel referensi ke null tidak merusak objek.

   referToSample = null;

berarti 'referToSample' hanya menunjuk ke nol, objek tetap sama dan variabel referensi lainnya berfungsi dengan baik. Jadi untuk 'sample' yang tidak menunjuk ke null dan memiliki objek yang valid

   sample.append("A");

bekerja dengan baik. Tetapi jika kami mencoba menambahkan null ke 'referToSample', NullPointException akan ditampilkan. Itu adalah,

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

Itulah mengapa Anda mendapatkan NullPointerException di cuplikan kode ketiga Anda.

NCA
sumber
0

Setiap kali kata kunci baru digunakan, itu Membuat Objek di Heap

1) Contoh StringBuilder = new StringBuilder ();

2) StringBuilder referToSample = sample;

Dalam 2) Referensi referSample dibuat pada sampel objek yang sama

jadi referToSample = null; adalah Nulling Only the referSample Reference tidak memberikan efek pada sampel, itulah sebabnya Anda tidak mendapatkan Pengecualian NULL Pointer Berkat Koleksi Sampah Java

nitesh
sumber
0

Mudah saja, Java tidak memiliki referensi lewat, itu hanya melewati referensi objek.

Peerapat A
sumber
2
Semantik penerusan parameter sebenarnya tidak ada hubungannya dengan situasi seperti yang dijelaskan.
Michael Myers