NullPointerException คืออะไรและฉันจะแก้ไขได้อย่างไร


210

ข้อยกเว้นตัวชี้ Null ( java.lang.NullPointerException) คืออะไรและทำให้เกิดอะไร

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

คำตอบ:


3764

เมื่อคุณประกาศตัวแปรอ้างอิง (เช่นวัตถุ) คุณกำลังสร้างตัวชี้ไปยังวัตถุจริงๆ พิจารณารหัสต่อไปนี้ที่คุณประกาศตัวแปรประเภทดั้งเดิมint:

int x;
x = 10;

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

แต่เมื่อคุณพยายามที่จะประกาศประเภทอ้างอิงมีบางสิ่งที่แตกต่างเกิดขึ้น ใช้รหัสต่อไปนี้:

Integer num;
num = new Integer(10);

บรรทัดแรกประกาศตัวแปรที่มีชื่อnumแต่จริง ๆ แล้วยังไม่มีค่าดั้งเดิม แต่จะมีตัวชี้ (เพราะประเภทนั้นIntegerเป็นประเภทการอ้างอิง) เมื่อคุณยังไม่ได้บอกว่าจะให้ชี้ไปที่ Java ตั้งค่าnullซึ่งหมายความว่า " ฉันกำลังชี้ไปที่ไม่มีอะไร "

ในบรรทัดที่สองnewคีย์เวิร์ดถูกใช้เพื่อสร้างอินสแตนซ์ (หรือสร้าง) วัตถุชนิดIntegerและตัวแปรตัวชี้numถูกกำหนดให้กับIntegerวัตถุนั้น

NullPointerExceptionเกิดขึ้นเมื่อคุณประกาศตัวแปร แต่ไม่ได้สร้างวัตถุและกำหนดให้กับตัวแปรก่อนที่จะพยายามที่จะใช้เนื้อหาของตัวแปร (เรียกว่าdereferencing ) ดังนั้นคุณกำลังชี้ไปที่สิ่งที่ไม่มีอยู่จริง

โดยปกติการลดความผิดพลาดจะเกิดขึ้นเมื่อใช้.เพื่อเข้าถึงวิธีการหรือเขตข้อมูลหรือใช้[เพื่อทำดัชนีอาร์เรย์

ถ้าคุณพยายามที่จะ dereference ก่อนที่จะสร้างวัตถุที่คุณได้รับnum NullPointerExceptionในกรณีเล็ก ๆ น้อย ๆ คอมไพเลอร์จะตรวจจับปัญหาและแจ้งให้คุณทราบว่า " num may not have been initialized," แต่บางครั้งคุณอาจเขียนโค้ดที่ไม่ได้สร้างวัตถุโดยตรง

ตัวอย่างเช่นคุณอาจมีวิธีการดังต่อไปนี้:

public void doSomething(SomeObject obj) {
   //do something to obj
}

ในกรณีนี้คุณไม่ได้สร้างวัตถุobjแต่สมมติว่ามันถูกสร้างขึ้นก่อนที่doSomething()วิธีการที่ถูกเรียก หมายเหตุเป็นไปได้ที่จะเรียกวิธีการเช่นนี้:

doSomething(null);

ซึ่งในกรณีนี้คือobj nullหากวิธีการนี้มีวัตถุประสงค์เพื่อทำบางสิ่งบางอย่างกับวัตถุที่ส่งผ่านมันมีความเหมาะสมที่จะโยนNullPointerExceptionเพราะมันเป็นข้อผิดพลาดของโปรแกรมเมอร์และโปรแกรมเมอร์จะต้องใช้ข้อมูลนั้นเพื่อวัตถุประสงค์ในการแก้จุดบกพร่อง กรุณาใส่ชื่อของตัวแปรวัตถุในข้อความข้อยกเว้นเช่น

Objects.requireNonNull(a, "a");

อีกวิธีหนึ่งอาจมีบางกรณีที่วัตถุประสงค์ของวิธีการนี้ไม่เพียง แต่จะดำเนินการกับวัตถุที่ส่งผ่านเท่านั้นดังนั้นพารามิเตอร์ null อาจยอมรับได้ ในกรณีนี้คุณจะต้องตรวจสอบพารามิเตอร์ว่างและทำงานต่างกัน คุณควรอธิบายเรื่องนี้ในเอกสารประกอบ ตัวอย่างเช่นdoSomething()สามารถเขียนเป็น:

/**
  * @param obj An optional foo for ____. May be null, in which case 
  *  the result will be ____.
  */
public void doSomething(SomeObject obj) {
    if(obj == null) {
       //do something
    } else {
       //do something else
    }
}

สุดท้ายวิธีระบุข้อยกเว้น & สาเหตุโดยใช้ Stack Trace

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

Sonar ที่มี findbugs สามารถตรวจจับ NPE สามารถจับโซนาร์ยกเว้นตัวชี้โมฆะที่เกิดจาก JVM แบบไดนามิก


558
วิธีที่ดีที่สุดในการหลีกเลี่ยงข้อยกเว้นประเภทนี้คือตรวจสอบค่าว่างเสมอเมื่อคุณไม่ได้สร้างวัตถุด้วยตนเอง หากผู้เรียกใช้ผ่าน null แต่ null ไม่ใช่อาร์กิวเมนต์ที่ถูกต้องสำหรับวิธีการนั้นจะเป็นการโยนข้อยกเว้นกลับไปที่ผู้โทรเนื่องจากเป็นความผิดของผู้โทร การเพิกเฉยอินพุตที่ไม่ถูกต้องอย่างเงียบ ๆ และการไม่ทำอะไรในวิธีนี้คือคำแนะนำที่แย่มากเพราะซ่อนปัญหา
Boann

104
ฉันจะเพิ่มข้อสังเกตเกี่ยวกับการโพสต์นี้อธิบายว่าได้รับมอบหมายแม้วิทยาการสามารถก่อให้เกิดสารเคมีที่เมื่อใช้ Autoboxing: int a=bสามารถโยน NPE Integerถ้าเป็นข มีหลายกรณีที่ทำให้เกิดความสับสนในการดีบัก
Simon Fischer

58
เป็นไปได้หรือไม่ที่จะจับ NPE ที่ถูกโยนโดย webapp จากเว็บเบราว์เซอร์ได้หรือไม่มันจะแสดงในแหล่งที่มาของหน้าการดูจากเว็บเบราว์เซอร์หรือไม่
Sid

76
ใช่ตรวจสอบว่าวัตถุเท่ากับโมฆะก่อนที่คุณจะเรียกใช้เมธอดหรือลองเข้าถึงตัวแปรที่อาจมี บางครั้งการสร้างรหัสของคุณสามารถช่วยหลีกเลี่ยงข้อยกเว้นตัวชี้โมฆะ เช่นเมื่อตรวจสอบสตริงอินพุตด้วยสตริงคงที่คุณควรเริ่มต้นด้วยสตริงคงที่เช่นที่นี่: if ("SomeString" .equals (inputString)) {} // แม้ว่า inputString จะว่างเปล่าก็ตาม ดังนั้นจึงมีหลายสิ่งที่คุณสามารถทำได้เพื่อพยายามที่จะปลอดภัย
โรส

78
วิธีเพิ่มเติมในการหลีกเลี่ยงNullPointerExceptionปัญหาในรหัสของคุณคือการใช้@Nullableและการ@NotNullเพิ่มความคิดเห็น คำตอบต่อไปนี้มีข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้ แม้ว่าคำตอบนี้จะเฉพาะเจาะจงเกี่ยวกับ IntelliJ IDE แต่ก็ยังสามารถใช้กับเครื่องมืออื่น ๆ ตามที่อธิบายไว้ในความคิดเห็นของคุณ (BTW ผมไม่ได้รับอนุญาตให้แก้ไขคำตอบนี้โดยตรงบางทีผู้เขียนสามารถเพิ่มได้หรือไม่)
Arjan Mels

879

NullPointerExceptions เป็นข้อยกเว้นที่เกิดขึ้นเมื่อคุณพยายามใช้การอ้างอิงที่ชี้ไปยังตำแหน่งในหน่วยความจำ (null) ไม่ว่าจะเป็นการอ้างอิงวัตถุ NullPointerExceptionเรียกวิธีการในการอ้างอิงโมฆะหรือพยายามที่จะเข้าถึงข้อมูลของอ้างอิงโมฆะจะเรียก นี่เป็นวิธีที่พบได้บ่อยที่สุด แต่มีวิธีอื่นที่แสดงไว้ในNullPointerExceptionหน้า javadoc

น่าจะเป็นโค้ดตัวอย่างที่เร็วที่สุดที่ฉันสามารถอธิบายเพื่อเป็นNullPointerException:

public class Example {

    public static void main(String[] args) {
        Object obj = null;
        obj.hashCode();
    }

}

ในภายในบรรทัดแรกmainผมตั้งค่าอย่างชัดเจนObjectอ้างอิงเท่ากับobj nullหมายความว่าฉันมีการอ้างอิง แต่ไม่ได้ชี้ไปที่วัตถุใด ๆ หลังจากนั้นฉันพยายามที่จะรักษาข้อมูลอ้างอิงราวกับว่ามันชี้ไปที่วัตถุโดยการเรียกวิธีการที่มัน นี่เป็นผลลัพธ์NullPointerExceptionเนื่องจากไม่มีรหัสที่จะดำเนินการในตำแหน่งที่การอ้างอิงกำลังชี้

(นี่เป็นเรื่องทางเทคนิค แต่ฉันคิดว่ามันพูดถึง: การอ้างอิงที่ชี้ไปที่โมฆะไม่เหมือนกับตัวชี้ C ที่ชี้ไปยังตำแหน่งหน่วยความจำที่ไม่ถูกต้องตัวชี้โมฆะไม่ได้ชี้ไปที่ใดซึ่งแตกต่างอย่างละเอียดกว่า ชี้ไปยังตำแหน่งที่ไม่ถูกต้อง)


49
ฉันเข้าใจทุกสิ่งที่คุณเขียนที่นั่น แต่เพียงเพราะฉันได้รับการเข้ารหัสในขณะที่และรู้ว่า 'ตัวชี้' และ 'การอ้างอิง' คืออะไร (และเป็นโมฆะอะไรสำหรับเรื่องนั้น) เมื่อฉันพยายามที่จะดำน้ำในคำอธิบายเช่นนั้นนักเรียนของฉันมองฉันข้ามเพราะมีพื้นหลังไม่เพียงพอ
mmr

33
@mmr: ขอบคุณสำหรับคำติชมคุณทำให้เป็นจุดที่ถูกต้อง เป็นเรื่องยากบนอินเทอร์เน็ตที่จะตัดสินว่าใครอยู่ที่ไหนและระดับใดที่ปลอดภัยในการเริ่มต้นคำอธิบาย ฉันจะลองแก้ไขอีกครั้ง
Bill the Lizard

22
วิธีการทั่วไปที่จะได้รับ NullPointerException ในทางปฏิบัติจะลืมที่จะเริ่มต้นอย่างชัดเจนตัวแปรสมาชิกอย่างอื่นที่ไม่ใช่nullก่อนที่จะใช้มันเช่นนี้ ด้วยตัวแปรโลคอลคอมไพเลอร์จะตรวจจับข้อผิดพลาดนี้ แต่ในกรณีนี้มันจะไม่เกิดขึ้น บางทีนั่นอาจจะเป็นประโยชน์เพิ่มเติมสำหรับคำตอบของคุณ?
Ilmari Karonen

6
@EJP ฉันคิดว่าคะแนนของคุณถูกต้องดังนั้นฉันจึงได้อัปเดตคำตอบให้ชัดเจนยิ่งขึ้นและเพื่อหลีกเลี่ยงการพูดว่า 'คะแนนเป็นโมฆะ' ในที่ที่มันทำ
Steve Powell

5
@StevePowell ฉันระบุมานานแล้วว่าฉันไม่ต้องการคำตอบที่จะเปลี่ยนแปลง โปรดเคารพเจตนาของผู้เขียนต้นฉบับ
Bill the Lizard

696

NullPointerException คืออะไร

สถานที่ที่ดีที่จะเริ่มเป็นJavaDocs พวกเขาได้รับความคุ้มครองนี้:

โยนทิ้งเมื่อแอปพลิเคชันพยายามใช้ null ในกรณีที่จำเป็นต้องใช้วัตถุ เหล่านี้รวมถึง:

  • การเรียกใช้วิธีการอินสแตนซ์ของวัตถุ null
  • การเข้าถึงหรือแก้ไขฟิลด์ของวัตถุ null
  • รับความยาวของโมฆะราวกับว่ามันเป็นอาร์เรย์
  • การเข้าถึงหรือการปรับเปลี่ยนสล็อตเป็นโมฆะราวกับว่ามันเป็นอาร์เรย์
  • การโยนค่า NULL เหมือนกับเป็นค่า Throwable

แอปพลิเคชันควรโยนอินสแตนซ์ของคลาสนี้เพื่อระบุการใช้งานวัตถุ null ที่ผิดกฎหมาย

เป็นกรณีที่ถ้าคุณพยายามใช้การอ้างอิงแบบ null ด้วยsynchronizedซึ่งจะทำให้เกิดข้อยกเว้นนี้ตาม JLS :

SynchronizedStatement:
    synchronized ( Expression ) Block
  • มิฉะนั้นถ้าค่าของ Expression เป็นโมฆะ a NullPointerExceptionจะถูกโยนทิ้ง

ฉันจะแก้ไขได้อย่างไร

NullPointerExceptionเพื่อให้คุณมี คุณจะแก้ไขได้อย่างไร ลองมาตัวอย่างง่ายๆที่พ่นNullPointerException:

public class Printer {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public void print() {
        printString(name);
    }

    private void printString(String s) {
        System.out.println(s + " (" + s.length() + ")");
    }

    public static void main(String[] args) {
        Printer printer = new Printer();
        printer.print();
    }
}

ระบุค่า Null

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

Exception in thread "main" java.lang.NullPointerException
    at Printer.printString(Printer.java:13)
    at Printer.print(Printer.java:9)
    at Printer.main(Printer.java:19)

ที่นี่เราเห็นว่ามีการยกเว้นข้อยกเว้นในบรรทัดที่ 13 (ในprintStringวิธีการ) ดูที่เส้นและการตรวจสอบที่มีค่า null โดยการเพิ่มงบการเข้าสู่ระบบหรือใช้ดีบัก เราพบว่าsเป็นโมฆะและการเรียกใช้lengthเมธอดนั้นจะทำให้เกิดข้อยกเว้น เราจะเห็นว่าโปรแกรมหยุดการโยนข้อยกเว้นเมื่อs.length()ถูกลบออกจากวิธีการ

ติดตามค่าที่มาจากเหล่านี้

ตรวจสอบว่าค่านี้มาจากที่ใด โดยทำตามผู้เรียกเมธอดเราจะเห็นว่าsถูกส่งผ่านด้วยprintString(name)ในprint()เมธอดและthis.nameเป็นโมฆะ

ติดตามตำแหน่งที่ควรตั้งค่าเหล่านี้

this.nameตั้งอยู่ที่ไหน ในsetName(String)วิธีการ ด้วยการดีบักเพิ่มเติมเราจะเห็นว่าวิธีนี้ไม่ได้ถูกเรียกใช้เลย หากมีการเรียกวิธีการตรวจสอบให้แน่ใจว่าได้ตรวจสอบลำดับที่เรียกวิธีการเหล่านี้และวิธีการตั้งค่านั้นไม่ได้ถูกเรียกใช้หลังจากวิธีการพิมพ์

นี้ก็เพียงพอที่จะทำให้เรามีวิธีการแก้ปัญหา: เพิ่มการเรียกก่อนที่จะเรียกprinter.setName()printer.print()

การแก้ไขอื่น ๆ

ตัวแปรสามารถมีค่าเริ่มต้น (และsetNameสามารถป้องกันไม่ให้ถูกตั้งค่าเป็นโมฆะ):

private String name = "";

อย่างใดอย่างหนึ่งprintหรือprintStringวิธีการที่สามารถตรวจสอบ nullตัวอย่างเช่น:

printString((name == null) ? "" : name);

หรือคุณสามารถออกแบบคลาสเพื่อให้name มีค่าไม่เป็นโมฆะ :

public class Printer {
    private final String name;

    public Printer(String name) {
        this.name = Objects.requireNonNull(name);
    }

    public void print() {
        printString(name);
    }

    private void printString(String s) {
        System.out.println(s + " (" + s.length() + ")");
    }

    public static void main(String[] args) {
        Printer printer = new Printer("123");
        printer.print();
    }
}

ดูสิ่งนี้ด้วย:

ฉันยังไม่พบปัญหา

หากคุณพยายามที่จะแก้ปัญหาและยังไม่มีวิธีแก้ปัญหาคุณสามารถโพสต์คำถามเพื่อขอความช่วยเหลือเพิ่มเติม แต่ให้แน่ใจว่าได้รวมสิ่งที่คุณได้ลองไปแล้ว อย่างน้อยที่สุดให้รวมสแต็คเทรซในคำถามและทำเครื่องหมายหมายเลขบรรทัดสำคัญในรหัส นอกจากนี้ให้ลองลดความซับซ้อนของรหัสก่อน (ดูSSCCE )


44
+1 ดีที่มีตัวอย่างซึ่งรวมถึงการผ่านสแต็กเทรซ เป็นสิ่งสำคัญที่จะแสดงว่าทำไมการอ่านจึงมีความสำคัญสำหรับการดีบัก NPE (และเหตุผลที่เรามักจะมองหา stacktrace เมื่อมีคนโพสต์คำถามเกี่ยวกับข้อผิดพลาด)
เดนนิสเม้ง

16
คุณพูดถึงการแก้ไขข้อบกพร่อง ... มันทำงานอย่างไร ฉันค้นคว้าหัวข้อนี้มาระยะหนึ่งแล้ว แต่ไม่พบอะไรเลย ฉันแน่ใจว่าคุณครูที่น่าอัศจรรย์อย่างคุณสามารถสอนให้ฉันได้ในไม่ช้า! ขอบคุณมาก! :-)
Ruchir Baronia

15
@RuchirBaronia โปรแกรมดีบั๊กช่วยให้คุณสามารถผ่านโปรแกรมทีละบรรทัดเพื่อดูวิธีการที่เรียกว่าและวิธีการเปลี่ยนแปลงตัวแปร IDEs ควรมีเครื่องมือในการทำเช่นนี้ ดูตัวอย่างvogella.com/tutorials/EclipseDebugging/article.html
fgb

15
@RuchirBaronia คุณตั้งค่าเบรกพอยต์ในวิธีการรอบ ๆ NullPointerExceptions เท่าที่เห็นใน stacktrace และตรวจสอบค่าของตัวแปรเทียบกับสิ่งที่คุณคาดว่าพวกเขาจะ หากคุณรู้ว่าตัวแปรนั้นเป็นโมฆะเมื่อไม่ควรเป็นเช่นนั้นคุณสามารถตั้งค่าเบรกพอยต์รอบ ๆ รหัสที่เปลี่ยนแปลงค่า นอกจากนี้ยังมีจุดพักตามเงื่อนไขที่คุณสามารถใช้ซึ่งจะบอกคุณเมื่อมีการเปลี่ยนแปลงค่า
fgb

6
การตั้งค่าวัตถุสตริงเป็นสตริงว่างเปล่าเป็นค่าเริ่มต้นของพวกเขาถือว่าเป็นแนวปฏิบัติที่ไม่ดี
เล็ก ๆ

501

คำถาม: อะไรทำให้NullPointerException(NPE)

ในขณะที่คุณควรรู้ประเภท Java จะแบ่งออกเป็นชนิดดั้งเดิม ( boolean, intฯลฯ ) และประเภทของการอ้างอิง ประเภทการอ้างอิงใน Java อนุญาตให้คุณใช้ค่าพิเศษnullซึ่งเป็นวิธีการบอกว่า "ไม่มีวัตถุ" ของ Java

A NullPointerExceptionถูกส่งไปที่รันไทม์เมื่อใดก็ตามที่โปรแกรมของคุณพยายามใช้ a nullราวกับว่าเป็นการอ้างอิงจริง ตัวอย่างเช่นถ้าคุณเขียนสิ่งนี้:

public class Test {
    public static void main(String[] args) {
        String foo = null;
        int length = foo.length();   // HERE
    }
}

คำสั่งที่ระบุว่า "ที่นี่" เป็นไปเพื่อพยายามที่จะเรียกใช้length()วิธีการในการอ้างอิงและนี้จะโยนnullNullPointerException

มีหลายวิธีที่คุณสามารถใช้เป็นค่าที่จะส่งผลให้null NullPointerExceptionในความเป็นจริงสิ่งเดียวที่คุณสามารถทำได้ด้วย a nullโดยไม่ทำให้ NPE คือ:

  • กำหนดให้กับตัวแปรอ้างอิงหรืออ่านจากตัวแปรอ้างอิง
  • กำหนดให้กับองค์ประกอบอาร์เรย์หรืออ่านจากองค์ประกอบอาร์เรย์ (โดยที่การอ้างอิงอาร์เรย์เองนั้นไม่ใช่แบบ null!)
  • ส่งผ่านเป็นพารามิเตอร์หรือส่งคืนผลลัพธ์หรือ
  • ทดสอบการใช้==หรือผู้ประกอบการหรือ!=instanceof

คำถาม: ฉันจะอ่าน stacktrace NPE ได้อย่างไร

สมมติว่าฉันรวบรวมและเรียกใช้โปรแกรมข้างต้น:

$ javac Test.java 
$ java Test
Exception in thread "main" java.lang.NullPointerException
    at Test.main(Test.java:4)
$

การสังเกตครั้งแรก: การรวบรวมสำเร็จ! ปัญหาในโปรแกรมไม่ใช่ข้อผิดพลาดในการรวบรวม มันเป็นข้อผิดพลาดรันไทม์ (IDE บางตัวอาจเตือนโปรแกรมของคุณมักจะเกิดข้อยกเว้น ... แต่javacคอมไพเลอร์มาตรฐานไม่ได้)

การสังเกตครั้งที่สอง: เมื่อฉันรันโปรแกรมมันจะแสดงผลสองบรรทัดของ "gobbledy-gook" ไม่ถูกต้อง!! นั่นไม่ใช่ gobbledy-gook เป็น stacktrace ... และให้ข้อมูลสำคัญที่จะช่วยคุณติดตามข้อผิดพลาดในรหัสของคุณหากคุณใช้เวลาอ่านอย่างระมัดระวัง

ดังนั้นเรามาดูสิ่งที่มันบอกว่า:

Exception in thread "main" java.lang.NullPointerException

บรรทัดแรกของการติดตามสแต็กจะบอกคุณหลายสิ่ง:

  • มันบอกชื่อของเธรด Java ที่มีข้อผิดพลาดเกิดขึ้น สำหรับโปรแกรมอย่างง่ายที่มีหนึ่งเธรด (เช่นนี้) มันจะเป็น "main" มาต่อกันที่ ...
  • มันบอกคุณถึงชื่อเต็มของข้อยกเว้นที่ถูกโยนทิ้ง java.lang.NullPointerExceptionกล่าวคือ
  • หากข้อยกเว้นมีข้อความแสดงข้อผิดพลาดที่เกี่ยวข้องนั่นจะเป็นผลลัพธ์หลังจากชื่อข้อยกเว้น NullPointerExceptionเป็นสิ่งผิดปกติในส่วนนี้เนื่องจากไม่ค่อยมีข้อความแสดงข้อผิดพลาด

บรรทัดที่สองเป็นสิ่งสำคัญที่สุดในการวินิจฉัย NPE

at Test.main(Test.java:4)

สิ่งนี้บอกเราหลายสิ่ง:

  • "ที่ Test.main" บอกว่าเราอยู่ในmainวิธีการTestเรียน
  • "Test.java:4" ให้ชื่อไฟล์แหล่งที่มาของชั้นเรียนและมันบอกเราว่าคำสั่งที่เกิดขึ้นนี้อยู่ในบรรทัดที่ 4 ของไฟล์

หากคุณนับบรรทัดในไฟล์ด้านบนบรรทัด 4 คือบรรทัดที่ฉันติดป้ายกำกับด้วยความคิดเห็น "ที่นี่"

โปรดทราบว่าในตัวอย่างที่ซับซ้อนมากขึ้นจะมีบรรทัดจำนวนมากในการติดตามสแต็ก NPE แต่คุณสามารถมั่นใจได้ว่าบรรทัดที่สอง (บรรทัดแรก "ที่") จะบอกคุณว่า NPE ถูกส่งไปที่ไหน1

กล่าวโดยย่อการติดตามสแต็กจะบอกให้เราทราบอย่างชัดเจนว่าคำสั่งใดของโปรแกรมที่ส่ง NPE ไป

1 - ไม่จริงเลย มีสิ่งที่เรียกว่าข้อยกเว้นซ้อนอยู่ ...

คำถาม: ฉันจะติดตามสาเหตุของข้อยกเว้น NPE ในรหัสของฉันได้อย่างไร

นี่คือส่วนที่ยาก คำตอบสั้น ๆ คือการใช้การอนุมานแบบลอจิคัลกับหลักฐานที่จัดทำโดยการติดตามสแต็คซอร์สโค้ดและเอกสารประกอบ API ที่เกี่ยวข้อง

ลองมาอธิบายด้วยตัวอย่างง่ายๆ (ด้านบน) ก่อน เราเริ่มต้นด้วยการดูบรรทัดที่การติดตามสแต็กได้บอกกับเราว่าเป็นที่ที่ NPE เกิดขึ้น:

int length = foo.length(); // HERE

มันจะโยน NPE ได้อย่างไร?

ในความเป็นจริงมีเพียงวิธีหนึ่งที่มันสามารถเกิดขึ้นได้ถ้ามีค่าfoo nullจากนั้นเราพยายามเรียกใช้length()วิธีการnullและ ... BANG!

แต่ (ฉันได้ยินคุณพูด) จะเกิดอะไรขึ้นถ้า NPE ถูกโยนลงไปในlength()การเรียกเมธอด?

หากเกิดเหตุการณ์นี้ขึ้นการติดตามสแต็กจะดูแตกต่างกัน บรรทัด "at" บรรทัดแรกจะบอกว่ามีข้อยกเว้นเกิดขึ้นในบางบรรทัดในjava.lang.Stringคลาสและบรรทัด 4 ของTest.javaจะเป็นบรรทัด "at" ที่สอง

แล้วมันnullมาจากไหน? ในกรณีนี้มันชัดเจนและเป็นสิ่งที่เราต้องทำเพื่อแก้ไข (กำหนดค่าที่ไม่ใช่ค่า null ให้กับfoo)

ตกลงดังนั้นลองทำตัวอย่างที่ซับซ้อนกว่านี้เล็กน้อย นี้จะต้องมีการหักตรรกะ

public class Test {

    private static String[] foo = new String[2];

    private static int test(String[] bar, int pos) {
        return bar[pos].length();
    }

    public static void main(String[] args) {
        int length = test(foo, 1);
    }
}

$ javac Test.java 
$ java Test
Exception in thread "main" java.lang.NullPointerException
    at Test.test(Test.java:6)
    at Test.main(Test.java:10)
$ 

ดังนั้นตอนนี้เรามีสองบรรทัด "ที่" คนแรกสำหรับบรรทัดนี้:

return args[pos].length();

และอันที่สองสำหรับบรรทัดนี้:

int length = test(foo, 1);

ดูที่บรรทัดแรกมันจะโยน NPE ได้อย่างไร มีสองวิธี:

  • ถ้าค่าของการbarเป็นnullแล้วbar[pos]จะโยน NPE
  • ถ้าค่าของbar[pos]ถูกnullแล้วโทรlength()ที่มันจะโยน NPE

ต่อไปเราต้องทราบว่าสถานการณ์ใดอธิบายถึงสิ่งที่เกิดขึ้นจริง เราจะเริ่มต้นด้วยการสำรวจสิ่งแรก:

ในกรณีที่ไม่barมาจากไหน? มันเป็นพารามิเตอร์ของtestการเรียกเมธอดและถ้าเราดูว่าtestถูกเรียกมาอย่างไรเราจะเห็นว่ามันมาจากfooตัวแปรคงที่ นอกจากนี้เราสามารถเห็นได้อย่างชัดเจนว่าเราเริ่มต้นfooเป็นค่าที่ไม่เป็นโมฆะ นั่นเพียงพอที่จะยกเลิกคำอธิบายนี้อย่างไม่แน่นอน (ตามทฤษฎีแล้วบางอย่างอาจเปลี่ยน fooเป็นnull... แต่นั่นไม่ได้เกิดขึ้นที่นี่)

แล้วสถานการณ์ที่สองของเราล่ะ ทีนี้, เราเห็นได้ว่านั่นposคือ1, นั่นหมายความว่าfoo[1]ต้องเป็นnullเช่นนั้น เป็นไปได้ไหม

แน่นอนมันเป็น! และนั่นคือปัญหา เมื่อเราเริ่มต้นเช่นนี้:

private static String[] foo = new String[2];

เราจัดสรร a String[]ด้วยสององค์ประกอบที่เริ่มต้นnullได้ หลังจากนั้นเรายังไม่ได้เปลี่ยนเนื้อหาของfoo... ดังนั้นจะยังคงเป็นfoo[1]null


425

nullมันก็เหมือนกับการที่คุณกำลังพยายามที่จะเข้าถึงวัตถุซึ่งเป็น ลองพิจารณาตัวอย่างด้านล่าง:

TypeA objA;

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

ดูตัวอย่างด้านล่างนี้เช่นกัน:

String a = null;
System.out.println(a.toString()); // NullPointerException will be thrown

1
ถ้าเราให้ System.out.println (a.length ()); // NullPointerException จะถูกโยนทิ้งเพื่อข้ามสิ่งนี้เราสามารถจัดการกับลอง catch catch ขอบคุณ
Vijaya Varma Lanke

359

ข้อยกเว้นตัวชี้ null ถูกส่งออกมาเมื่อแอปพลิเคชันพยายามใช้ null ในกรณีที่จำเป็นต้องใช้วัตถุ เหล่านี้รวมถึง:

  1. การเรียกวิธีการอินสแตนซ์ของnullวัตถุ
  2. การเข้าถึงหรือแก้ไขฟิลด์ของnullวัตถุ
  3. รับความยาวของnullราวกับว่ามันเป็นอาร์เรย์
  4. การเข้าถึงหรือแก้ไขสล็อตของnullราวกับว่ามันเป็นอาร์เรย์
  5. การขว้างปาnullราวกับว่ามันเป็นค่า Throwable

แอปพลิเคชันควรโยนอินสแตนซ์ของคลาสนี้เพื่อบ่งชี้การใช้งานnullวัตถุอย่างผิดกฎหมาย

การอ้างอิง: http://docs.oracle.com/javase/8/docs/api/java/lang/NullPointerException.html


12
ให้มันง่าย ๆ ฉันชอบคำตอบนี้เพิ่มสิ่งนี้ถ้าคุณคิดว่าถูกต้อง - การเข้าถึงคุณลักษณะที่ไม่ได้กำหนดค่าเริ่มต้นของวัตถุ
Emiliano

5
@Emiliano - เพียงเข้าถึงแอตทริบิวต์ที่ได้รับการเตรียมใช้งานไม่ได้ทำให้เกิด NPE >> คือสิ่งที่คุณทำ << ด้วยค่าแอตทริบิวต์ที่ไม่ได้กำหนดค่าเริ่มต้นที่ทำให้ NPE
Stephen C

1
หากคุณต้องการกรณีเพิ่มเติมได้ที่: 1) ใช้nullเป็นเป้าหมายของการเป็นsynchronizedบล็อก 2) ใช้nullเป็นเป้าหมายของการให้และการแกะกล่องswitch null
สตีเฟ่นซี

333

nullชี้เป็นหนึ่งในจุดที่ไม่มีที่ไหนเลย เมื่อคุณอ้างถึงตัวชี้pคุณจะพูดว่า "ให้ข้อมูลที่ตำแหน่งที่เก็บไว้ใน" p "ให้ฉันเมื่อpเป็นnullตัวชี้ตำแหน่งที่จัดเก็บในpคือnowhereคุณกำลังพูดว่า" ให้ข้อมูลที่ตำแหน่ง 'ไม่มีที่ไหนเลย' " null pointer exceptionเห็นได้ชัดว่ามันไม่สามารถทำเช่นนี้จึงพ่น

โดยทั่วไปเป็นเพราะบางสิ่งไม่ได้เริ่มต้นอย่างถูกต้อง


2
เรากำลังสร้างฐานข้อมูลหรือไม่? -> NULLเขียนเป็นภาษาnullจาวา และมันเป็นเรื่องที่อ่อนไหว
bvdb

3
"ตัวชี้ NULL เป็นตัวชี้ไปที่ไหน"ฉันไม่เห็นด้วย ตัวชี้ Null ไม่ได้ชี้ไปที่ใดพวกมันชี้ไปที่ค่า Null
TheRealChx101

2
@ TheRealChx101 ตัวชี้ Null และตัวชี้ไปยังค่า Null เป็นสิ่งที่แตกต่างกัน - ตัวชี้ Null ไม่ได้ชี้ไปที่ค่า Null สมมติว่าคุณมีตัวชี้ไปยังตัวชี้: ตัวชี้ A ชี้ไปที่ตัวชี้ B และตัวชี้ B เป็นค่าว่าง ในกรณีนี้ตัวชี้ A ชี้ไปที่ค่า Null และตัวชี้ B เป็นตัวชี้ Null
MrZebra

321

จำนวนมากของคำอธิบายที่มีอยู่แล้วที่จะอธิบายว่ามันเกิดขึ้นและวิธีการที่จะแก้ไขได้ แต่คุณยังควรปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดที่จะหลีกเลี่ยงNullPointerExceptions ที่ทั้งหมด

ดูเพิ่มเติม: รายการปฏิบัติที่ดีที่สุด

ฉันจะเพิ่มสิ่งที่สำคัญมากใช้ประโยชน์จากfinalตัวปรับแต่ง การใช้ตัวดัดแปลง "final" เมื่อใดก็ตามที่เกี่ยวข้องใน Java

สรุป:

  1. ใช้โมดิfinalฟายเออร์เพื่อบังคับใช้การเริ่มต้นที่ดี
  2. หลีกเลี่ยงการคืนค่า Null ในเมธอดตัวอย่างเช่นการส่งคืนคอลเลกชันที่ว่างเปล่าเมื่อใช้ได้
  3. ใช้คำอธิบายประกอบ@NotNullและ@Nullable
  4. ล้มเหลวอย่างรวดเร็วและใช้ asserts เพื่อหลีกเลี่ยงการเผยแพร่วัตถุ null ผ่านแอปพลิเคชันทั้งหมดเมื่อไม่ควรเป็น null
  5. ใช้เท่ากับวัตถุที่รู้จักกันก่อน: if("knownObject".equals(unknownObject)
  6. ชอบมากกว่าvalueOf()toString()
  7. ใช้ null ปลอดภัยวิธีStringUtilsStringUtils.isEmpty(null)
  8. ใช้ Java 8 ทางเลือกเป็นค่าส่งคืนในเมธอดคลาสทางเลือกจัดเตรียมโซลูชันสำหรับการแทนค่าทางเลือกแทนการอ้างอิงแบบ null

4
ในโครงการ j2ee ข้อยกเว้น Nullpointer เป็นเรื่องธรรมดามากบางกรณีการอ้างอิงตัวแปรมีค่า Null ดังนั้นคุณควรตรวจสอบการเริ่มต้นของตัวแปรอย่างถูกต้องและในระหว่างคำสั่งเงื่อนไขคุณควรตรวจสอบการตั้งค่าสถานะหรือการอ้างอิงที่มี null หรือไม่ชอบ: - = 0) {รหัส ur ที่ใช้ธง}
Amaresh Pattanayak

14
เป็นมูลค่าการกล่าวขวัญว่า IDE บางตัว (เช่น Eclipse) เสนอการวิเคราะห์ค่าความเป็นโมฆะอัตโนมัติโดยยึดตามคำอธิบายประกอบที่ปรับแต่งได้ (เช่น@Nullableตามที่ระบุไว้ด้านบน) และเตือนเกี่ยวกับข้อผิดพลาดที่อาจเกิดขึ้น นอกจากนี้ยังเป็นไปได้ที่จะอนุมานและสร้างคำอธิบายประกอบดังกล่าว (เช่น IntelliJ สามารถทำได้) ขึ้นอยู่กับโครงสร้างรหัสที่มีอยู่
Jan Chimiak

4
สิ่งแรกที่ควรทำคือก่อนใช้วัตถุที่เป็นโมฆะคุณควรตรวจสอบว่ามันเป็นโมฆะหรือไม่if (obj==null)หากใช้เป็นโมฆะคุณควรเขียนโค้ดเพื่อจัดการกับสิ่งนั้นด้วย
Lakmal Vithanage

4
IMO จะดีกว่าเพื่อหลีกเลี่ยงการส่งคืนออบเจ็กต์ null ในวิธีการเมื่อเป็นไปได้และใช้คำอธิบายประกอบเมื่อไม่อนุญาตให้ใช้พารามิเตอร์อินพุตว่างในสัญญาโดยลดจำนวน´if (obj == null) code ในรหัสและปรับปรุง การอ่านรหัส
LG

4
อ่านสิ่งนี้ ... ก่อนที่คุณจะยอมรับ "การปฏิบัติที่ดีที่สุด" เหล่านี้เป็นจริง: satisfice.com/blog/archives/27
Stephen C

316

ใน Java ทุกอย่าง (ไม่รวมประเภทดั้งเดิม) อยู่ในรูปแบบของคลาส

หากคุณต้องการใช้วัตถุใด ๆ คุณมีสองขั้นตอนดังนี้

  1. ประกาศ
  2. การเริ่มต้น

ตัวอย่าง:

  • ประกาศ: Object object;
  • การเริ่มต้น: object = new Object();

เหมือนกันสำหรับแนวคิดอาร์เรย์:

  • ประกาศ: Item item[] = new Item[5];
  • การเริ่มต้น: item[0] = new Item();

หากคุณไม่ได้ให้ส่วนเริ่มต้นแล้วNullPointerExceptionเกิดขึ้น


3
NullPointerException มักจะเกิดขึ้นเมื่อเรียกเมธอดของอินสแตนซ์ตัวอย่างเช่นถ้าคุณประกาศการอ้างอิง แต่ไม่ได้ชี้ไปที่อินสแตนซ์ใด ๆ NullPointerException จะเกิดขึ้นเมื่อคุณเรียกเมธอด เช่น: YourClass ref = null; // หรือ ref = anotherRef; // แต่ elseRef ไม่ได้ชี้อินสแตนซ์ใด ๆ ref.someMethod (); // มันจะขว้าง NullPointerException โดยทั่วไปแก้ไขด้วยวิธีนี้: ก่อนที่จะเรียกวิธีการตรวจสอบว่าการอ้างอิงเป็นโมฆะ เช่น: if (yourRef! = null) {yourRef.someMethod (); }
sunhang

2
หรือใช้การดักจับข้อยกเว้น: เช่นลอง {yourRef.someMethod (); } catch (NullPointerException e) {// TODO}
sunhang


315

ข้อยกเว้นตัวชี้โมฆะคือตัวบ่งชี้ที่คุณกำลังใช้วัตถุโดยไม่เริ่มต้น

ตัวอย่างเช่นด้านล่างเป็นชั้นเรียนของนักเรียนซึ่งจะใช้ในรหัสของเรา

public class Student {

    private int id;

    public int getId() {
        return this.id;
    }

    public setId(int newId) {
        this.id = newId;
    }
}

รหัสด้านล่างให้ข้อยกเว้นตัวชี้โมฆะ

public class School {

    Student student;

    public School() {
        try {
            student.getId();
        }
        catch(Exception e) {
            System.out.println("Null pointer exception");
        }
    }
}

เพราะคุณใช้studentแต่คุณลืมที่จะเริ่มต้นมันเหมือนในรหัสที่ถูกต้องที่แสดงด้านล่าง:

public class School {

    Student student;

    public School() {
        try {
            student = new Student();
            student.setId(12);
            student.getId();
        }
        catch(Exception e) {
            System.out.println("Null pointer exception");
        }
    }
}

7
ในขณะที่นี่เป็นตัวอย่างที่ดีฉันขอให้มันเพิ่มคำถามที่ยังไม่ได้ครอบคลุมโดยคำตอบอื่น ๆ ทั้งหมดหรือไม่
Mysticial

13
มันไม่เหมาะสมที่จะใช้คำว่า "uninitialized" ที่นี่ ตัวอย่างที่คุณแสดงคือ "เริ่มต้นจริง" และถูกกำหนดค่าเริ่มต้นด้วย null สำหรับตัวแปรเริ่มต้นคอมไพเลอร์จะบ่นกับคุณ
Adrian Shum

2
NPE อาจเป็นตัวบ่งชี้ว่าคุณกำลังใช้ฟิลด์ที่ไม่มีการกำหนดค่าเริ่มต้น อาจเป็นตัวบ่งชี้ว่าคุณกำลังทำสิ่งอื่น ๆ การขยายไปสู่สาเหตุเดียวเช่นนี้ไม่ได้ช่วยให้ใครบางคนแก้ปัญหา NPE ... ถ้าสาเหตุที่แท้จริงไม่ใช่สาเหตุนี้
สตีเฟ่นซี

309

ในJavaตัวแปรทั้งหมดที่คุณประกาศเป็นจริง "อ้างอิง" ไปยังวัตถุ (หรือดั้งเดิม) และไม่ใช่วัตถุเอง

เมื่อคุณพยายามที่จะดำเนินการวิธีการหนึ่งวัตถุการอ้างอิงขอให้วัตถุที่มีชีวิตในการดำเนินการวิธีการนั้น แต่ถ้าการอ้างอิงคือการอ้างอิง NULL (ไม่มีอะไร, เป็นศูนย์, เป็นโมฆะ, นาดะ) จากนั้นก็ไม่มีทางที่วิธีการได้รับการดำเนินการ จากนั้นรันไทม์แจ้งให้คุณทราบโดยการโยน NullPointerException

การอ้างอิงของคุณคือ "การชี้" เป็นโมฆะดังนั้นจึงเป็น "Null -> Pointer"

วัตถุอาศัยอยู่ในพื้นที่หน่วยความจำ VM และวิธีเดียวในการเข้าถึงโดยใช้thisการอ้างอิง นำตัวอย่างนี้:

public class Some {
    private int id;
    public int getId(){
        return this.id;
    }
    public setId( int newId ) {
        this.id = newId;
    }
}

และที่อื่นในรหัสของคุณ:

Some reference = new Some();    // Point to a new object of type Some()
Some otherReference = null;     // Initiallly this points to NULL

reference.setId( 1 );           // Execute setId method, now private var id is 1

System.out.println( reference.getId() ); // Prints 1 to the console

otherReference = reference      // Now they both point to the only object.

reference = null;               // "reference" now point to null.

// But "otherReference" still point to the "real" object so this print 1 too...
System.out.println( otherReference.getId() );

// Guess what will happen
System.out.println( reference.getId() ); // :S Throws NullPointerException because "reference" is pointing to NULL remember...

สิ่งนี้เป็นสิ่งสำคัญที่ต้องรู้ - เมื่อไม่มีการอ้างอิงถึงวัตถุอีกต่อไป (ในตัวอย่างด้านบนเมื่อreferenceและotherReferenceทั้งคู่ชี้ไปที่โมฆะ) ดังนั้นวัตถุนั้น "ไม่สามารถเข้าถึงได้" ไม่มีทางที่เราจะสามารถทำงานกับมันได้ดังนั้นวัตถุนี้พร้อมที่จะรวบรวมขยะและในบางจุด VM จะเพิ่มหน่วยความจำที่ใช้โดยวัตถุนี้และจะจัดสรรอีก


280

อีกเหตุการณ์หนึ่งที่เกิดNullPointerExceptionขึ้นเมื่อมีการประกาศอาร์เรย์ของวัตถุจากนั้นพยายามที่จะตรวจสอบองค์ประกอบภายในของมันทันที

String[] phrases = new String[10];
String keyPhrase = "Bird";
for(String phrase : phrases) {
    System.out.println(phrase.equals(keyPhrase));
}

NPE เฉพาะนี้สามารถหลีกเลี่ยงได้หากมีการกลับคำสั่งเปรียบเทียบ กล่าวคือใช้.equalsกับวัตถุที่ไม่เป็นโมฆะที่รับประกันได้

องค์ประกอบทั้งหมดภายในอาร์เรย์จะเริ่มต้นได้ที่ค่าเริ่มต้นของพวกเขาร่วมกัน ; สำหรับประเภทของอาร์เรย์วัตถุใด ๆ nullนั่นหมายความว่าองค์ประกอบทั้งหมดที่มี

คุณต้องเริ่มต้นองค์ประกอบในอาร์เรย์ก่อนที่จะเข้าถึงหรือยกเลิกการลงทะเบียนพวกเขา

String[] phrases = new String[] {"The bird", "A bird", "My bird", "Bird"};
String keyPhrase = "Bird";
for(String phrase : phrases) {
    System.out.println(phrase.equals(keyPhrase));
}

การดำเนินการกับวัตถุที่ไม่ได้กำหนดค่าเริ่มต้นที่ระดับอินสแตนซ์ (ไม่ใช่ระดับคลาส) จะนำไปสู่ ​​NullPointerException การดำเนินการจะต้องมีอินสแตนซ์ที่เฉพาะเจาะจง ถ้าการดำเนินการอยู่ในระดับคลาสการเรียกใช้เมธอดแบบคงที่บนวัตถุที่ไม่มีการกำหนดค่าเริ่มต้นจะไม่ส่งข้อยกเว้น NullPointerException แม้แต่อ็อบเจ็กต์คลาส wrapper ดั้งเดิมจะส่ง NullPointerException
Shailendra Singh

1. NullPointerException เป็น RuntimeException ซึ่งหมายความว่าจะปรากฏขึ้นเมื่อโปรแกรมของคุณกำลังทำงานคุณจะไม่ได้รับการรวบรวมเวลา! :( แต่ IDE ส่วนใหญ่ช่วยให้คุณค้นพบสิ่งนี้ 2. ลดการใช้คำสำคัญ 'null' ในงบการมอบหมายให้น้อยที่สุด :) URL อ้างอิง:
tomj0101

@ tomj0101 ฉันไม่ชัดเจนว่าทำไมคุณถึงแสดงความคิดเห็นว่า ... แต่ถึงจุดที่สองของคุณรูปแบบก่อนที่Optionalจะกลับเป็นโมฆะ คำหลักใช้ได้ดี การรู้วิธีป้องกันมันเป็นสิ่งสำคัญ สิ่งนี้เสนอเหตุการณ์ที่เกิดขึ้นทั่วไปอย่างหนึ่งและวิธีการบรรเทา
Makoto

NullPointerException เป็นข้อยกเว้นเวลาทำงานซึ่งไม่แนะนำให้จับมัน แต่ควรหลีกเลี่ยง
Shomu

2
@Shomu: ณ จุดใดฉันยังแนะนำว่าควรจะจับ?
Makoto
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.