เหตุใดจึงไม่โยน 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เพราะสิ่งที่ฉันเห็นและเข้าใจจากสองตัวอย่างแรกคือถ้าวัตถุสองชิ้นอ้างถึงวัตถุเดียวกันถ้าเราเปลี่ยนค่าใด ๆ มันก็จะสะท้อนไปที่อื่นด้วยเพราะทั้งสองชี้ไปที่ การอ้างอิงหน่วยความจำเดียวกัน แล้วทำไมกฎนั้นถึงไม่ใช้ที่นี่? ถ้าฉันกำหนดnullให้ ReferToSample ตัวอย่างก็ควรเป็นโมฆะด้วยและควรโยน NullPointerException แต่ไม่ได้โยนไปทำไม?


31
sampleยังsampleอยู่ referToSampleคุณเปลี่ยนแปลงเท่านั้น
Dave Newton

25
โหวตขึ้น / ติดดาว! คำถามพื้นฐานมาก แต่นี่เป็นตัวอย่างที่สวยงามในการอธิบายปัญหาของคุณและถามคำถามได้ดี
Ryan Ransford

15
คำศัพท์จุดหนึ่งในคำถามของคุณ: คุณอ้างถึงsampleและreferToSampleเป็นวัตถุแต่ไม่ใช่วัตถุเป็นตัวแปร ตัวแปรสามารถถือการอ้างอิงไปยังวัตถุได้ แต่ไม่ใช่ตัวของมันเอง มันเป็นความแตกต่างที่ลึกซึ้ง แต่โดยพื้นฐานแล้วมันคือสาระสำคัญของความสับสนของคุณ
Daniel Pryden

1
ช่วยให้คิดว่าตัวแปรออบเจ็กต์เป็นเพียงตัวชี้ ผู้ประกอบการใด ๆ ที่ทำหน้าที่เกี่ยวกับตัวแปร ( volatile, final, =, ==... ) เมื่อนำไปใช้ตัวแปรวัตถุมีผลต่อตัวชี้ไม่ใช่วัตถุที่มันหมายถึง
MikeFHay

2
@Arpit ฉันไม่เห็นด้วยอย่างเคารพ มีแนวคิดใหญ่ซ่อนอยู่ในคำถามนี้นั่นคือความแตกต่างระหว่างวัตถุและการอ้างอิงถึงวัตถุนั้น โดยส่วนใหญ่เราไม่จำเป็นต้อง (หรือไม่ต้องการ) ที่จะตระหนักถึงความแตกต่างนี้และนักออกแบบภาษาก็พยายามอย่างหนักที่จะซ่อนมันจากเรา เพียงแค่นึกถึงอาร์กิวเมนต์ pass-by-reference ใน 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 แสดงให้เห็นว่า annulling referToSampleไม่ทำลายการอ้างอิงของsampleการ StringBuilder 00012345ที่

1 การพิจารณา GC ทำให้ไม่น่าเชื่อ


@commit หากสิ่งนี้ตอบคำถามของคุณโปรดคลิกเครื่องหมายถูกด้านล่างลูกศรโหวตทางด้านซ้ายของข้อความด้านบน
Ray Britton

@RayBritton ฉันชอบที่ผู้คนคิดว่าคำตอบที่ได้รับการโหวตสูงสุดคือคำตอบที่จำเป็นสำหรับการตอบคำถาม แม้ว่าการนับนั้นมีความสำคัญ แต่ก็ไม่ใช่เมตริกเดียว -1 และ -2 คำตอบสามารถได้รับการยอมรับเพียงเพราะพวกเขาช่วย OP มากที่สุด
nanofarad

11
hashCode()คือไม่ได้เป็นสิ่งเดียวกับที่อยู่ในหน่วยความจำ
เควิน Panko

6
โดยทั่วไปแล้วรหัสประจำตัว hashCode จะได้มาจากตำแหน่งหน่วยความจำของ Object ในครั้งแรกที่เรียกเมธอด จากนั้น hashCode จะได้รับการแก้ไขและจดจำแม้ว่าตัวจัดการหน่วยความจำจะตัดสินใจย้ายวัตถุไปยังตำแหน่งหน่วยความจำอื่น
Holger

3
คลาสที่แทนที่hashCodeมักจะกำหนดให้ส่งคืนค่าที่ไม่มีส่วนเกี่ยวข้องกับที่อยู่หน่วยความจำ hashCodeฉันคิดว่าคุณสามารถทำให้จุดของคุณเกี่ยวกับการอ้างอิงวัตถุกับวัตถุที่ไม่ต้องกล่าวถึง ประเด็นปลีกย่อยเกี่ยวกับวิธีรับที่อยู่หน่วยความจำสามารถทิ้งไว้ได้อีกวัน
Kevin Panko

62

เริ่มแรกเป็นไปตามที่คุณกล่าวreferToSampleอ้างถึงsampleดังที่แสดงด้านล่าง:

1. สถานการณ์ที่ 1:

ReferToSample หมายถึงตัวอย่าง

2. สถานการณ์ที่ 1 (ต่อ):

ReferToSample.append ("B")

  • ตามที่referToSampleอ้างถึงsampleดังนั้นจึงต่อท้าย "B" ในขณะที่คุณเขียน

    referToSample.append("B")

สิ่งเดียวกันที่เกิดขึ้นในScenario2:

แต่ใน 3. Scenario3:ดังที่ hexafraction กล่าวไว้

เมื่อคุณกำหนดnullให้referToSampleเมื่อมันถูกหมายsampleมันไม่ได้เปลี่ยนค่าแทนมันก็แบ่งอ้างอิงจากsampleและตอนนี้ก็ชี้ไม่มีที่ไหนเลย ดังแสดงด้านล่าง:

เมื่อ ReferToSample = null

ตอนนี้ไม่มีreferToSampleจุดใดเลยดังนั้นในขณะที่คุณreferToSample.append("A");จะไม่มีค่าหรือการอ้างอิงใด ๆ ที่สามารถต่อท้าย A ได้ดังนั้นมันจะโยนNullPointerExceptionดังนั้นจึงจะโยน

แต่sampleยังคงเหมือนกับที่คุณได้เริ่มต้นด้วย

StringBuilder sample = new StringBuilder(); ดังนั้นมันจึงถูกสร้างขึ้นมาดังนั้นตอนนี้จึงสามารถต่อท้าย A ได้และจะไม่โยน NullPointerException


5
แผนภาพที่ดี ช่วยแสดงให้เห็น
nanofarad

แผนภาพที่ดี แต่หมายเหตุที่ด้านล่างควรเป็น "ตอนนี้ ReferToSample ไม่ได้อ้างถึง" "" เพราะเมื่อคุณreferToSample = sample;อ้างถึงตัวอย่างคุณเพียงแค่คัดลอกที่อยู่ของสิ่งที่อ้างอิง
Khaled.K

17

สรุป: คุณกำหนดค่าว่างให้กับตัวแปรอ้างอิงไม่ใช่ให้กับวัตถุ

ในตัวอย่างหนึ่งคุณเปลี่ยนสถานะของวัตถุที่อ้างถึงโดยตัวแปรอ้างอิงสองตัว เมื่อสิ่งนี้เกิดขึ้นตัวแปรอ้างอิงทั้งสองจะสะท้อนถึงการเปลี่ยนแปลง

ในอีกตัวอย่างหนึ่งคุณเปลี่ยนการอ้างอิงที่กำหนดให้กับตัวแปรหนึ่งตัว แต่สิ่งนี้ไม่มีผลกับอ็อบเจ็กต์เองดังนั้นตัวแปรที่สองซึ่งยังคงอ้างถึงอ็อบเจ็กต์ดั้งเดิมจะไม่สังเกตเห็นการเปลี่ยนแปลงใด ๆ ในสถานะอ็อบเจ็กต์


สำหรับ "กฎ" เฉพาะของคุณ:

หากวัตถุสองชิ้นที่อ้างถึงวัตถุเดียวกันถ้าเราเปลี่ยนค่าใด ๆ มันก็จะสะท้อนไปที่อื่นด้วยเพราะทั้งสองชี้ไปที่การอ้างอิงหน่วยความจำเดียวกัน

อีกครั้งคุณอ้างถึงการเปลี่ยนสถานะของวัตถุหนึ่งที่ตัวแปรทั้งสองอ้างถึง

แล้วทำไมกฎนั้นถึงไม่ใช้ที่นี่? ถ้าฉันกำหนด null ให้กับ referenceToSample ตัวอย่างก็ควรเป็นโมฆะด้วยและมันควรจะโยน nullPointerException แต่มันไม่ใช่การขว้างทำไม?

อีกครั้งคุณเปลี่ยนการอ้างอิงของตัวแปรเดียวซึ่งไม่มีผลกับการอ้างอิงอย่างแน่นอนของตัวแปรอื่นอย่างแน่นอน

นี่เป็นการกระทำสองอย่างที่แตกต่างกันอย่างสิ้นเชิงและจะส่งผลสองอย่างที่แตกต่างกันอย่างสิ้นเชิง


3
@commit: เป็นแนวคิดพื้นฐาน แต่สำคัญที่อยู่ภายใต้ Java ทั้งหมดและเมื่อคุณเห็นคุณจะไม่มีวันลืม
Hovercraft Full Of Eels

8

ดูแผนภาพง่ายๆนี้:

แผนภาพ

เมื่อคุณเรียกวิธีการบนreferToSampleแล้ว[your object]มีการปรับปรุงเพื่อให้มันมีผลกระทบsampleมากเกินไป แต่เมื่อคุณพูดreferToSample = nullแล้วคุณเพียงแค่เปลี่ยนสิ่งที่referToSample หมายถึงการ


3

ที่นี่ 'ตัวอย่าง' และ 'อ้างอิงถึงตัวอย่าง' กำลังอ้างอิงวัตถุเดียวกันนั่นคือแนวคิดของตัวชี้ต่าง ๆ ที่เข้าถึงตำแหน่งหน่วยความจำเดียวกัน ดังนั้นการกำหนดตัวแปรอ้างอิงหนึ่งตัวแปรให้เป็น null จึงไม่ทำลายออบเจ็กต์

   referToSample = null;

หมายถึง 'referenceToSample' เท่านั้นที่ชี้ไปที่ null วัตถุยังคงเหมือนเดิมและตัวแปรอ้างอิงอื่น ๆ ทำงานได้ดี ดังนั้นสำหรับ 'ตัวอย่าง' ซึ่งไม่ได้ชี้ไปที่ null และมีวัตถุที่ถูกต้อง

   sample.append("A");

ใช้งานได้ดี แต่ถ้าเราพยายามต่อท้าย null กับ 'referenceToSample' มันจะแสดง NullPointException นั่นคือ,

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

นั่นคือเหตุผลที่คุณมี NullPointerException ในข้อมูลโค้ดที่สามของคุณ


0

เมื่อใดก็ตามที่มีการใช้คีย์เวิร์ดใหม่จะสร้าง Object ที่ Heap

1) ตัวอย่าง StringBuilder = ใหม่ StringBuilder ();

2) StringBuilder ReferToSample = ตัวอย่าง;

ใน 2) การอ้างอิงของ referenceSample ถูกสร้างขึ้นบนตัวอย่างวัตถุเดียวกัน

ดังนั้นReferToSample = null; เป็น Nulling เฉพาะการอ้างอิงตัวอย่างเท่านั้นที่ไม่มีผลต่อตัวอย่างนั่นคือสาเหตุที่คุณไม่ได้รับ NULL Pointer Exception ด้วย Garbage Collection ของ Java


0

ง่ายมาก Java ไม่ได้ผ่านการอ้างอิงเพียงแค่ส่งผ่านการอ้างอิงวัตถุ


2
ความหมายการส่งผ่านพารามิเตอร์ไม่เกี่ยวข้องกับสถานการณ์ตามที่อธิบายไว้
Michael Myers
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.