หลีกเลี่ยง! = ข้อความสั่งโมฆะ


4014

ผมใช้จำนวนมากที่จะหลีกเลี่ยงobject != nullNullPointerException

มีทางเลือกที่ดีสำหรับสิ่งนี้หรือไม่?

ตัวอย่างเช่นฉันมักจะใช้:

if (someobject != null) {
    someobject.doCalc();
}

การตรวจสอบนี้สำหรับNullPointerExceptionสำหรับsomeobjectวัตถุในตัวอย่างข้างต้น

โปรดทราบว่าคำตอบที่ยอมรับอาจล้าสมัยโปรดดูhttps://stackoverflow.com/a/2386013/12943สำหรับวิธีการล่าสุด


120
@Shervin Encouraging nulls ทำให้โค้ดไม่เข้าใจและน่าเชื่อถือน้อยกว่า
Tom Hawtin - tackline

44
ผู้ประกอบการเอลวิสได้รับการเสนอแต่ดูเหมือนว่ามันจะไม่อยู่ในJava 7 เลวร้ายเกินไป, ?. ?: และ? [] เป็นตัวช่วยเวลาอย่างไม่น่าเชื่อ
สกอตต์

72
การไม่ใช้ null จะดีกว่าคำแนะนำอื่น ๆ ส่วนใหญ่ที่นี่ โยนข้อยกเว้นอย่าส่งคืนหรืออนุญาตค่า null BTW - คำหลัก 'ยืนยัน' ไม่มีประโยชน์เพราะถูกปิดใช้งานโดยค่าเริ่มต้น ใช้กลไกความล้มเหลวที่เปิดใช้งานตลอดเวลา
ianpojman

13
นี่คือเหตุผลหนึ่งว่าทำไมตอนนี้ฉันถึงใช้ Scala ใน Scala ทุกอย่างไม่เป็นโมฆะ หากคุณต้องการอนุญาตให้ส่งผ่านหรือส่งคืน "ไม่มีอะไร" คุณต้องใช้ตัวเลือก [T] แทนการโต้แย้งหรือประเภทกลับ
Thekwasti

11
@thSoft แท้จริงน่ากลัว Scala ของOptionประเภทจะ marred nullโดยไม่เต็มใจภาษาที่จะควบคุมหรือไม่อนุญาต ฉันพูดถึงเรื่องนี้ในแฮกเกอร์ข่าวและลงคะแนนและบอกว่า "ไม่มีใครใช้โมฆะใน Scala อยู่ดี" คาดเดาสิ่งที่ฉันกำลังหาโมฆะใน Scala ที่เพื่อนร่วมงานเขียน ใช่พวกเขากำลัง "ทำผิด" และต้องได้รับการศึกษาเกี่ยวกับเรื่องนี้ แต่ความจริงยังคงเป็นระบบประเภทของภาษาที่ควรปกป้องฉันจากสิ่งนี้และมันไม่ได้ :(
Andres F.

คำตอบ:


2642

สิ่งนี้สำหรับฉันดูเหมือนว่าเป็นปัญหาที่พบได้ทั่วไปพอสมควรที่ผู้พัฒนาระดับต้นถึงระดับกลางมักเผชิญในบางประเด็น: พวกเขาไม่รู้หรือไม่เชื่อถือสัญญาที่พวกเขาเข้าร่วม นอกจากนี้เมื่อเขียนรหัสของตัวเองพวกเขามักจะพึ่งพาการคืนค่าโมฆะเพื่อระบุสิ่งที่ต้องการให้ผู้โทรตรวจสอบค่า Null

หากต้องการใช้อีกวิธีหนึ่งมีสองกรณีที่การตรวจสอบโมฆะเกิดขึ้น:

  1. เมื่อ null เป็นคำตอบที่ถูกต้องในเงื่อนไขของสัญญา และ

  2. ในกรณีที่มันไม่ตอบสนองที่ถูกต้อง

(2) เป็นเรื่องง่าย ใช้assertข้อความสั่ง (ยืนยัน) หรืออนุญาตความล้มเหลว (ตัวอย่างเช่น NullPointerException ) การยืนยันเป็นคุณลักษณะ Java ที่มีการใช้งานต่ำซึ่งถูกเพิ่มใน 1.4 ไวยากรณ์คือ:

assert <condition>

หรือ

assert <condition> : <object>

โดยที่<condition>เป็นนิพจน์บูลีนและ<object>เป็นวัตถุที่toString()เอาต์พุตของเมธอดจะรวมอยู่ในข้อผิดพลาด

assertคำสั่งพ่นError( AssertionError) ถ้าเงื่อนไขไม่เป็นความจริง โดยค่าเริ่มต้น Java ละเว้นการยืนยัน คุณสามารถเปิดใช้งานการยืนยันด้วยการส่งตัวเลือก-eaไปที่ JVM คุณสามารถเปิดใช้งานและปิดใช้งานการยืนยันสำหรับแต่ละคลาสและแพ็คเกจ ซึ่งหมายความว่าคุณสามารถตรวจสอบรหัสด้วยการยืนยันในขณะที่การพัฒนาและการทดสอบและปิดการใช้งานในสภาพแวดล้อมการผลิตแม้ว่าการทดสอบของฉันแสดงให้เห็นว่าไม่มีผลกระทบต่อประสิทธิภาพจากการยืนยัน

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

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

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

ด้วยการไม่สะสมอาจเป็นเรื่องยาก ลองพิจารณาสิ่งนี้เป็นตัวอย่าง: หากคุณมีอินเตอร์เฟสเหล่านี้:

public interface Action {
  void doSomething();
}

public interface Parser {
  Action findAction(String userInput);
}

ที่ Parser รับอินพุตผู้ใช้แบบ raw และค้นหาสิ่งที่ต้องทำบางทีถ้าคุณกำลังใช้อินเตอร์เฟสบรรทัดคำสั่งสำหรับบางสิ่ง ตอนนี้คุณอาจทำสัญญาว่าจะส่งคืน null ถ้าไม่มีการกระทำที่เหมาะสม นั่นนำไปสู่การตรวจสอบโมฆะที่คุณกำลังพูดถึง

ทางเลือกอื่นคือไม่คืนค่า Null และใช้รูปแบบ Null Objectแทน:

public class MyParser implements Parser {
  private static Action DO_NOTHING = new Action() {
    public void doSomething() { /* do nothing */ }
  };

  public Action findAction(String userInput) {
    // ...
    if ( /* we can't find any actions */ ) {
      return DO_NOTHING;
    }
  }
}

เปรียบเทียบ:

Parser parser = ParserFactory.getParser();
if (parser == null) {
  // now what?
  // this would be an example of where null isn't (or shouldn't be) a valid response
}
Action action = parser.findAction(someInput);
if (action == null) {
  // do nothing
} else {
  action.doSomething();
}

ถึง

ParserFactory.getParser().findAction(someInput).doSomething();

ซึ่งเป็นการออกแบบที่ดีกว่ามากเพราะนำไปสู่รหัสที่กระชับยิ่งขึ้น

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

try {
    ParserFactory.getParser().findAction(someInput).doSomething();
} catch(ActionNotFoundException anfe) {
    userConsole.err(anfe.getMessage());
}

หรือถ้าคุณคิดว่ากลไก try / catch น่าเกลียดเกินไปไม่ทำอะไรเลยการกระทำเริ่มต้นของคุณควรให้ข้อเสนอแนะกับผู้ใช้

public Action findAction(final String userInput) {
    /* Code to return requested Action if found */
    return new Action() {
        public void doSomething() {
            userConsole.err("Action not found: " + userInput);
        }
    }
}

631
ฉันไม่เห็นด้วยกับข้อความของคุณสำหรับการกระทำ DO_NOTHING หากวิธีการค้นหาการกระทำไม่สามารถหาการกระทำได้การคืนค่า Null เป็นสิ่งที่ถูกต้อง คุณได้ "พบ" การกระทำในรหัสของคุณซึ่งไม่พบจริง ๆ ซึ่งละเมิดหลักการของวิธีการเพื่อหาการกระทำที่ใช้งานได้
MetroidFan2002

266
ฉันยอมรับว่าโมฆะถูกใช้มากเกินไปใน Java โดยเฉพาะกับรายการ apis มากมายน่าจะดีกว่าถ้าพวกเขาคืนค่ารายการ / แถว / คอลเลกชันที่ว่างเปล่าแทนที่จะเป็นค่าว่าง หลายครั้งจะใช้ null ซึ่งข้อยกเว้นควรถูกโยนแทน ควรโยนข้อยกเว้นหาก parser แยกวิเคราะห์ไม่ได้
Laplie Anderson

92
ตัวอย่างหลังนี่คือ IIRC รูปแบบการออกแบบ Null Object
Steven Evers

62
@Cshah (และ MetroidFan2002) ง่ายใส่ลงในสัญญาและจากนั้นเป็นที่ชัดเจนว่าการกระทำที่ส่งคืนไม่พบจะไม่ทำอะไรเลย หากนี่เป็นข้อมูลที่สำคัญสำหรับผู้โทรให้ระบุวิธีที่จะค้นพบว่าเป็นการกระทำที่ไม่พบ (เช่นจัดเตรียมวิธีการตรวจสอบว่าผลลัพธ์เป็นวัตถุ DO_NOTHING หรือไม่) อีกทางเลือกหนึ่งหากพบการกระทำโดยทั่วไปแล้วคุณยังไม่ควรคืนค่าว่าง แต่ให้ส่งข้อยกเว้นเป็นการเฉพาะเพื่อระบุเงื่อนไขนั้น - ซึ่งยังคงส่งผลให้มีรหัสที่ดีกว่า หากต้องการให้ระบุวิธีการส่งกลับบูลีนแยกต่างหากเพื่อตรวจสอบว่าการกระทำที่มีอยู่
Kevin Brock

35
กระชับไม่รหัสคุณภาพที่เท่าเทียมกัน ฉันขอโทษที่คุณคิดเช่นนั้น รหัสของคุณจะซ่อนสถานการณ์ที่ข้อผิดพลาดจะได้เปรียบ
gshauger

635

หากคุณใช้ (หรือวางแผนที่จะใช้) Java IDE เช่นJetBrains IntelliJ IDEA , EclipseหรือNetbeansหรือเครื่องมือเช่น findbugs คุณสามารถใช้คำอธิบายประกอบเพื่อแก้ปัญหานี้ได้

โดยทั่วไปคุณได้และ@Nullable@NotNull

คุณสามารถใช้ในวิธีการและพารามิเตอร์เช่นนี้

@NotNull public static String helloWorld() {
    return "Hello World";
}

หรือ

@Nullable public static String helloWorld() {
    return "Hello World";
}

ตัวอย่างที่สองจะไม่รวบรวม (ใน IntelliJ IDEA)

เมื่อคุณใช้helloWorld()ฟังก์ชันแรกในรหัสอื่น:

public static void main(String[] args)
{
    String result = helloWorld();
    if(result != null) {
        System.out.println(result);
    }
}

ตอนนี้ความคิดที่คอมไพเลอร์ IntelliJ จะบอกคุณว่าการตรวจสอบจะไม่ได้ผลเนื่องจากhelloWorld()ฟังก์ชั่นจะไม่กลับมาnullเลยทีเดียว

ใช้พารามิเตอร์

void someMethod(@NotNull someParameter) { }

ถ้าคุณเขียนสิ่งที่ชอบ:

someMethod(null);

สิ่งนี้จะไม่รวบรวม

ตัวอย่างล่าสุดที่ใช้ @Nullable

@Nullable iWantToDestroyEverything() { return null; }

ทำสิ่งนี้

iWantToDestroyEverything().something();

และคุณสามารถมั่นใจได้ว่าสิ่งนี้จะไม่เกิดขึ้น :)

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

ใน IntelliJ IDEA 10.5 ขึ้นไปจะเพิ่มการสนับสนุนสำหรับ@Nullable @NotNullการปรับใช้อื่น ๆ

ดูโพสต์บล็อกที่มีความยืดหยุ่นมากขึ้นและการกำหนดค่า @ Nullable / @ NotNull คำอธิบายประกอบ


120
@NotNull, @Nullableและคำอธิบายประกอบ nullness อื่น ๆ ที่เป็นส่วนหนึ่งของJSR 305 นอกจากนี้คุณยังสามารถใช้เพื่อตรวจสอบปัญหาที่อาจเกิดขึ้นด้วยเครื่องมือเช่นFindBugs
Jacek S

32
ฉันคิดว่ามันน่ารำคาญแปลกว่า@NotNullและอินเตอร์เฟซที่อาศัยอยู่ในแพคเกจ@Nullable com.sun.istack.internal(ผมคิดว่าผม com.sun เชื่อมโยงกับคำเตือนเกี่ยวกับการใช้ API ที่เป็นกรรมสิทธิ์.)
Jonik

20
การพกพาโค้ดไม่เป็นไปตามเจ็ทเบรนส์ฉันคิดว่าสองครั้ง (สี่เหลี่ยมจัตุรัส) ก่อนที่จะลงไปถึงระดับ IDE เหมือน Jacek S กล่าวว่าพวกเขาเป็นส่วนหนึ่งของ JSR ในทางใดก็ตามที่ฉันคิดว่าเป็น JSR303
Java Ka Baby

10
ฉันไม่คิดว่าการใช้คอมไพเลอร์ที่กำหนดเองเป็นทางออกที่ทำงานได้สำหรับปัญหานี้
Shivan Dragon

64
สิ่งที่ดีเกี่ยวกับคำอธิบายประกอบซึ่ง@NotNullและ@Nullableเป็นที่พวกเขาเป็นอย่างดีลดลงเมื่อรหัสแหล่งที่มาที่ถูกสร้างขึ้นโดยระบบที่ไม่เข้าใจพวกเขา ดังนั้นผลที่ตามมาคือข้อโต้แย้งว่ารหัสไม่ใช่พกพาอาจไม่ถูกต้อง - หากคุณใช้ระบบที่รองรับและเข้าใจคำอธิบายประกอบเหล่านี้คุณจะได้รับประโยชน์เพิ่มเติมจากการตรวจสอบข้อผิดพลาดที่เข้มงวดมิฉะนั้นคุณจะได้รับน้อยลง สร้างได้ดีและคุณภาพของโปรแกรมที่คุณใช้คือ THE SAME เนื่องจากคำอธิบายประกอบเหล่านี้ไม่ได้ถูกบังคับใช้ในขณะใช้งาน นอกจากนี้คอมไพเลอร์ทั้งหมดจะกำหนดเอง ;-)
AMN

321

หากไม่อนุญาตให้ใช้ค่า null

หากวิธีการของคุณถูกเรียกจากภายนอกให้เริ่มจากสิ่งนี้:

public void method(Object object) {
  if (object == null) {
    throw new IllegalArgumentException("...");
  }

จากนั้นในวิธีที่เหลือคุณจะรู้ว่าobjectไม่เป็นโมฆะ

ถ้ามันเป็นวิธีการภายใน (ไม่ใช่ส่วนหนึ่งของ API) เพียงแค่เอกสารว่ามันไม่สามารถเป็นโมฆะได้

ตัวอย่าง:

public String getFirst3Chars(String text) {
  return text.subString(0, 3);
}

อย่างไรก็ตามหากวิธีการของคุณเพียงแค่ส่งผ่านค่าบนและวิธีต่อไปผ่านไป ฯลฯ มันอาจได้รับปัญหา ในกรณีนั้นคุณอาจต้องการตรวจสอบอาร์กิวเมนต์ตามข้างต้น

ถ้าโมฆะได้รับอนุญาต

สิ่งนี้ขึ้นอยู่กับ หากพบว่าฉันมักจะทำสิ่งนี้:

if (object == null) {
  // something
} else {
  // something else
}

ดังนั้นฉันจึงแตกแขนงและทำสองสิ่งที่แตกต่างอย่างสิ้นเชิง ไม่มีข้อมูลโค้ดที่น่าเกลียดเพราะฉันต้องทำสองสิ่งที่แตกต่างกันขึ้นอยู่กับข้อมูล ตัวอย่างเช่นฉันควรทำงานกับอินพุตหรือฉันควรคำนวณค่าเริ่มต้นที่ดีหรือไม่


ที่จริงแล้วมันหายากสำหรับฉันที่จะใช้สำนวน " if (object != null && ..."

มันอาจจะง่ายกว่าที่จะให้คุณตัวอย่างถ้าคุณแสดงตัวอย่างของที่คุณมักจะใช้สำนวน


94
ประเด็นในการขว้าง IllegalArgumentException คืออะไร ฉันคิดว่า NullPointerException จะชัดเจนขึ้นและนั่นก็จะถูกโยนทิ้งหากคุณไม่ทำการตรวจสอบด้วยตนเอง ฉันจะใช้ยืนยันหรือไม่มีอะไรเลย
Axel

23
มันไม่น่าเป็นไปได้ที่ค่าอื่น ๆ ทั้งหมดจะไม่ยอมรับได้ คุณอาจมี IllegalArgumentException, OutOfRageException ฯลฯ เป็นต้นบางครั้งสิ่งนี้ก็สมเหตุสมผล ในบางครั้งคุณอาจสร้างคลาสข้อยกเว้นจำนวนมากที่ไม่เพิ่มค่าใด ๆ จากนั้นคุณเพียงใช้ IllegalArgumentException มันไม่มีเหตุผลที่จะมีข้อยกเว้นหนึ่งสำหรับอินพุตว่างและอีกอันสำหรับทุกสิ่ง
myplacedk

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

8
ช่องโหว่ความปลอดภัยหรือไม่? JDK เต็มไปด้วยรหัสเช่นนี้ หากคุณไม่ต้องการให้ผู้ใช้เห็น stacktraces ให้ปิดการใช้งาน ไม่มีใครบอกเป็นนัยว่าพฤติกรรมดังกล่าวไม่ได้รับการบันทึกไว้ MySQL เขียนเป็นภาษา C โดยที่ dereferencing ตัวชี้โมฆะเป็นพฤติกรรมที่ไม่ได้กำหนดไม่มีอะไรที่เหมือนกับการโยนข้อยกเว้น
fgb

8
throw new IllegalArgumentException("object==null")
Thorbjørn Ravn Andersen

238

ว้าวฉันเกือบจะเกลียดที่จะเพิ่มคำตอบอื่นเมื่อเรามี 57 วิธีที่แตกต่างกันที่จะแนะนำNullObject patternแต่ฉันคิดว่าบางคนที่สนใจในคำถามนี้อาจต้องการที่จะรู้ว่ามีข้อเสนอในตารางสำหรับ Java 7 เพื่อเพิ่ม"ปลอดภัย การจัดการ " - ไวยากรณ์ที่มีความคล่องตัวสำหรับตรรกะ if-not-เท่ากับ-null

ตัวอย่างที่ได้รับจาก Alex Miller มีลักษณะดังนี้:

public String getPostcode(Person person) {  
  return person?.getAddress()?.getPostcode();  
}  

?.หมายถึงเฉพาะ nullde-อ้างอิงระบุซ้ายถ้ามันไม่เป็นโมฆะมิฉะนั้นประเมินที่เหลือของการแสดงออกเป็น บางคนเช่นสมาชิก Java กองดิ๊กกำแพงและผู้มีสิทธิเลือกตั้งที่ Devoxxรักจริงๆข้อเสนอนี้ แต่มีความขัดแย้งมากเกินไปในบริเวณที่มันจริงจะส่งเสริมให้ใช้มากขึ้นของnullเป็นค่าแมวมอง


ปรับปรุง: ข้อเสนออย่างเป็นทางการสำหรับผู้ประกอบการ null ปลอดภัยใน Java 7 ถูกส่งมาภายใต้โครงการเหรียญ ไวยากรณ์แตกต่างจากตัวอย่างด้านบนเล็กน้อย แต่เป็นความคิดเดียวกัน


อัปเดต:ข้อเสนอโอเปอเรเตอร์ที่ปลอดภัยไม่ได้ทำให้เป็น Project Coin ดังนั้นคุณจะไม่เห็นไวยากรณ์นี้ใน Java 7


20
ฉันคิดว่ามันผิด ควรมีวิธีการระบุว่าตัวแปรที่กำหนดนั้นไม่ใช่ค่า NULL เสมอ
Thorbjørn Ravn Andersen

5
อัปเดต: ข้อเสนอจะไม่ทำให้ Java7 ดูblogs.sun.com/darcy/entry/project_coin_final_five
Boris Terzic

9
แนวคิดที่น่าสนใจ แต่ตัวเลือกของไวยากรณ์นั้นไร้สาระ; ฉันไม่ต้องการ codebase ที่เต็มไปด้วยเครื่องหมายคำถามซึ่งถูกตรึงอยู่กับการร่วมทุกข้อ
Rob

7
ตัวดำเนินการนี้มีอยู่ในGroovyดังนั้นผู้ที่ต้องการใช้จะยังคงมีตัวเลือกนั้นอยู่
Muhd

8
นี่เป็นความคิดที่ฉลาดที่สุดที่ฉันเคยเห็น ควรเพิ่มทุกภาษาที่สมเหตุสมผลของไวยากรณ์ C ฉันต้องการ "ปักหมุดเครื่องหมายคำถาม" ทุกที่แทนที่จะเลื่อนผ่านหน้าจอของเส้นหรือหลบ "คำสั่งป้องกัน" ตลอดทั้งวัน
Victor

196

หากไม่อนุญาตให้ใช้ค่าที่ไม่ได้กำหนด:

คุณอาจกำหนดค่า IDE ของคุณเพื่อเตือนคุณเกี่ยวกับการยกเลิกการอ้างอิงที่อาจเกิดขึ้น เช่นใน Eclipse ดูการตั้งค่า> Java> คอมไพเลอร์> ข้อผิดพลาด / คำเตือน / วิเคราะห์

หากอนุญาตค่าที่ไม่ได้กำหนด:

หากคุณต้องการกำหนด API ใหม่ที่มีค่าที่ไม่ได้กำหนดให้ใช้รูปแบบตัวเลือก (อาจคุ้นเคยกับภาษาที่ใช้งานได้) มันมีข้อดีดังต่อไปนี้:

  • มีการระบุไว้อย่างชัดเจนใน API ว่ามีอินพุตหรือเอาต์พุตอยู่หรือไม่
  • คอมไพเลอร์บังคับให้คุณจัดการกับเคส "undefined"
  • ตัวเลือกที่ monadจึงมีความจำเป็นที่จะต้องตรวจสอบอย่างละเอียด null เพียงใช้แผนที่ / foreach / getOrElse หรือ Combinator คล้ายกับการใช้อย่างปลอดภัยค่าไม่มี(ตัวอย่าง)

Java 8 มีOptionalคลาสในตัว(แนะนำ); สำหรับรุ่นก่อนหน้านี้มีทางเลือกห้องสมุดเช่นฝรั่ง 's OptionalหรือFunctionalJava ' Options แต่ก็เหมือนกับรูปแบบการใช้งานหลายรูปแบบการใช้ตัวเลือกใน Java (แม้ 8) จะส่งผลให้เกิดสำเร็จรูปบางส่วนซึ่งคุณสามารถลดการใช้ภาษา JVM ที่ละเอียดน้อยกว่าเช่น Scala หรือ Xtend

หากคุณต้องจัดการกับ API ซึ่งอาจส่งคืน nullคุณไม่สามารถทำอะไรได้มากใน Java Xtend และ Groovy มีตัวดำเนินการ Elvis ?:และตัวดำเนินการ dereference แบบ null-safe ?.แต่โปรดทราบว่าสิ่งนี้จะคืนค่า null ในกรณีของการอ้างอิงแบบ null ดังนั้นจึงเป็นเพียง "defers" การจัดการค่า null ที่เหมาะสม


22
แน่นอนรูปแบบตัวเลือกน่ากลัว เทียบเท่า Java บางอย่างอยู่ ฝรั่งมีรุ่น จำกัด ซึ่งเรียกว่าเป็นตัวเลือกซึ่งทำให้เนื้อหาส่วนใหญ่หมดไป ใน Haskell ลวดลายนี้มีชื่อว่าบางที
Ben Hardy

9
คลาสเสริมจะมีให้ใน Java 8
Pierre Henry

1
... และมันยังไม่มีแผนที่หรือ flatMap: download.java.net/jdk8/docs/api/java/util/Optional.html
thSoft

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

1
@Boann ถ้าใช้ด้วยความระมัดระวังคุณจะแก้ปัญหา NPE ของคุณทั้งหมด ถ้าไม่เช่นนั้นฉันเดาว่ามีปัญหา "การใช้งาน"
Louis F.

189

สำหรับสถานการณ์นี้เท่านั้น -

ไม่ตรวจสอบว่าตัวแปรเป็นโมฆะก่อนที่จะเรียกใช้เมธอด equals (สตริงเปรียบเทียบตัวอย่างด้านล่าง):

if ( foo.equals("bar") ) {
 // ...
}

จะส่งผลให้NullPointerExceptionถ้าfooไม่มี

คุณสามารถหลีกเลี่ยงได้ถ้าคุณเปรียบเทียบStringแบบนี้:

if ( "bar".equals(foo) ) {
 // ...
}

42
ฉันเห็นด้วย - เฉพาะในสถานการณ์นั้น ฉันไม่สามารถยืนโปรแกรมเมอร์ที่นำสิ่งนี้ไปสู่ระดับที่ไม่จำเป็นต่อไปและเขียนว่า (null! = myVar) ... เพียงแค่ดูน่าเกลียดสำหรับฉันและไม่มีจุดประสงค์!
Alex Worden

20
นี่คือตัวอย่างที่โดยเฉพาะอย่างยิ่งอาจจะใช้มากที่สุดของการปฏิบัติที่ดีโดยทั่วไป: <object that you know that is not null>.equals(<object that might be null>);ถ้าคุณรู้ว่ามันมักจะทำ มันใช้งานได้กับวิธีอื่นที่ไม่ใช่equalsถ้าคุณรู้ว่าสัญญาและวิธีการเหล่านั้นสามารถจัดการnullพารามิเตอร์ได้
Stef

9
นี่เป็นตัวอย่างแรกที่ฉันได้เห็นจากYoda เงื่อนไขที่เหมาะสมจริง ๆ
Erin Drummond

6
NullPointerExceptions ถูกส่งไปด้วยเหตุผล พวกเขาจะถูกโยนเพราะวัตถุเป็นโมฆะที่ไม่ควรจะเป็น มันเป็นงานโปรแกรมเมอร์ที่จะแก้ไขปัญหานี้ไม่ใช่ซ่อนปัญหา
โอลิเวอร์ Watkins

1
Try-Catch-DoNothing ซ่อนปัญหานี่เป็นแนวทางปฏิบัติที่ถูกต้องในการแก้ปัญหาน้ำตาลในภาษา
echox

167

ด้วย Java 8 มาใหม่ java.util.Optionalคลาสที่สามารถแก้ไขปัญหาบางอย่างได้ อย่างน้อยก็สามารถพูดได้ว่ามันช่วยปรับปรุงการอ่านรหัสและในกรณีของ API สาธารณะทำให้สัญญาของ API ชัดเจนขึ้นสำหรับนักพัฒนาไคลเอ็นต์

พวกเขาทำงานอย่างนั้น:

วัตถุทางเลือกสำหรับประเภทที่กำหนด ( Fruit) ถูกสร้างเป็นประเภทส่งคืนของวิธีการ มันอาจจะว่างเปล่าหรือมีFruitวัตถุ:

public static Optional<Fruit> find(String name, List<Fruit> fruits) {
   for (Fruit fruit : fruits) {
      if (fruit.getName().equals(name)) {
         return Optional.of(fruit);
      }
   }
   return Optional.empty();
}

ตอนนี้ดูรหัสนี้ที่เราค้นหารายการของFruit( fruits) สำหรับตัวอย่างผลไม้ที่กำหนด:

Optional<Fruit> found = find("lemon", fruits);
if (found.isPresent()) {
   Fruit fruit = found.get();
   String name = fruit.getName();
}

คุณสามารถใช้map()โอเปอเรเตอร์เพื่อทำการคำนวณ - หรือแยกค่าจาก - วัตถุทางเลือก orElse()ให้คุณจัดเตรียมทางเลือกสำหรับค่าที่หายไป

String nameOrNull = find("lemon", fruits)
    .map(f -> f.getName())
    .orElse("empty-name");

แน่นอนว่าการตรวจสอบค่าว่าง / ไม่ว่างยังเป็นสิ่งจำเป็น แต่อย่างน้อยผู้พัฒนาก็ตระหนักว่าค่าอาจว่างเปล่าและความเสี่ยงในการลืมตรวจสอบนั้นมี จำกัด

ใน API ที่สร้างขึ้นจากศูนย์โดยใช้Optionalเมื่อใดก็ตามที่ค่าส่งคืนอาจว่างเปล่าและส่งคืนวัตถุธรรมดาเฉพาะเมื่อไม่สามารถnull(แบบแผน) รหัสลูกค้าอาจละทิ้งการตรวจสอบค่า Null ของค่าส่งคืนวัตถุอย่างง่าย ...

แน่นอนว่าOptionalสามารถใช้เป็นอาร์กิวเมนต์เมธอดได้ซึ่งอาจเป็นวิธีที่ดีกว่าในการระบุอาร์กิวเมนต์เพิ่มเติมมากกว่า 5 หรือ 10 วิธีการโอเวอร์โหลดในบางกรณี

Optionalข้อเสนอวิธีการอื่น ๆ ที่สะดวกเช่นorElseที่อนุญาตให้ใช้ค่าเริ่มต้นและifPresentการทำงานที่มีการแสดงออกแลมบ์ดา

ผมขอเชิญคุณอ่านบทความนี้ (แหล่งที่มาหลักของการเขียนคำตอบนี้) ซึ่งในNullPointerException(และในชี้โมฆะทั่วไป) ที่มีปัญหาเช่นเดียวกับ (บางส่วน) วิธีการแก้ปัญหาโดยนำOptionalเป็นอย่างดีอธิบาย: Java ตัวเลือกวัตถุ


11
ฝรั่งของ Google นั้นมีนัยทางเลือกสำหรับ Java 6+
แบรดลีย์กอทฟริด

13
เป็นสิ่งสำคัญมากที่จะเน้นว่าการใช้ตัวเลือกเฉพาะกับ ifPresent () จะไม่เพิ่มมูลค่ามากไปกว่าการตรวจสอบ null ปกติ ค่าหลักคือมันเป็น monad ที่สามารถใช้ในกลุ่มฟังก์ชันของ map / flapMap ซึ่งได้ผลลัพธ์ที่คล้ายกับตัวดำเนินการ Elvis ใน Groovy ที่กล่าวถึงที่อื่น แม้ว่าจะไม่มีการใช้งานนี้ แต่ฉันพบว่าไวยากรณ์ orElse / orElseThrow นั้นมีประโยชน์มาก
Cornel Masson

บล็อกนี้มีรายการที่ดีเกี่ยวกับตัวเลือกwinterbe.com/posts/2015/03/15/avoid-null-checks-in-java
JohnC

4
เหตุใดผู้คนจึงมีแนวโน้มที่จะทำเช่นนี้if(optional.isPresent()){ optional.get(); }แทนoptional.ifPresent(o -> { ...})
Satyendra Kumar

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

125

ขึ้นอยู่กับชนิดของวัตถุที่คุณกำลังตรวจสอบคุณอาจสามารถใช้บางคลาสใน apache คอมมอนส์เช่น: apache คอมมอนส์ langและapache คอมมอนส์คอลเลกชัน

ตัวอย่าง:

String foo;
...
if( StringUtils.isBlank( foo ) ) {
   ///do something
}

หรือ (ขึ้นอยู่กับสิ่งที่คุณต้องตรวจสอบ):

String foo;
...
if( StringUtils.isEmpty( foo ) ) {
   ///do something
}

คลาส StringUtils เป็นหนึ่งเดียวเท่านั้น มีคลาสที่ค่อนข้างดีอยู่ในคอมมอนส์ที่ทำการจัดการอย่างปลอดภัยเป็นโมฆะ

ต่อไปนี้เป็นตัวอย่างของวิธีการใช้ null vallidation ใน JAVA เมื่อคุณรวมไลบรารี apache (commons-lang-2.4.jar)

public DOCUMENT read(String xml, ValidationEventHandler validationEventHandler) {
    Validate.notNull(validationEventHandler,"ValidationHandler not Injected");
    return read(new StringReader(xml), true, validationEventHandler);
}

และถ้าคุณใช้ Spring, Spring ก็มีฟังก์ชั่นเดียวกันในแพ็คเกจดูที่ library (spring-2.4.6.jar)

ตัวอย่างเกี่ยวกับวิธีใช้ classf แบบคงที่นี้จากฤดูใบไม้ผลิ (org.springframework.util.Assert)

Assert.notNull(validationEventHandler,"ValidationHandler not Injected");

5
นอกจากนี้คุณสามารถใช้เวอร์ชันทั่วไปจาก Apache Commons ซึ่งค่อนข้างมีประโยชน์ในช่วงเริ่มต้นของวิธีการตรวจสอบ params ที่ฉันค้นหา Validate.notNull (วัตถุ "วัตถุต้องไม่เป็นโมฆะ"); commons.apache.org/lang/apidocs/org/apache/commons/lang/…
monojohnny

@monojohnny ไม่ตรวจสอบความถูกต้องใช้คำสั่ง Assert เป็น? ฉันถามว่าเนื่องจาก Assert อาจเปิดใช้งาน / ปิดการใช้งานกับ JVM และแนะนำว่าอย่าใช้ในการผลิต
Kurapika

อย่าคิดอย่างนั้น - ผมเชื่อว่ามันเป็นเพียงแค่โยน RuntimeException ถ้าการตรวจสอบล้มเหลว
monojohnny

96
  • หากคุณพิจารณาว่าวัตถุไม่ควรเป็นโมฆะ (หรือเป็นข้อผิดพลาด) ให้ใช้การยืนยัน
  • หากวิธีการของคุณไม่ยอมรับ params โมฆะบอกว่ามันอยู่ใน javadoc และใช้การยืนยัน

คุณต้องตรวจสอบวัตถุ! = null เฉพาะในกรณีที่คุณต้องการจัดการกับกรณีที่วัตถุอาจเป็นโมฆะ ...

มีข้อเสนอที่จะเพิ่มคำอธิบายประกอบใหม่ใน Java7 เพื่อช่วยในการ null / notnull params: http://tech.puredanger.com/java7/#jsr308


3
ไม่มีไม่ได้ใช้ในการยืนยันรหัสการผลิต
phil294

91

ฉันเป็นแฟนของรหัส "ล้มเหลวอย่างรวดเร็ว" ถามตัวคุณเอง - คุณกำลังทำสิ่งที่มีประโยชน์ในกรณีที่พารามิเตอร์เป็นโมฆะหรือไม่ หากคุณไม่มีคำตอบที่ชัดเจนสำหรับสิ่งที่รหัสของคุณควรทำในกรณีนี้ ... นั่นคือไม่ควรเป็นโมฆะในตอนแรกให้เพิกเฉยและปล่อยให้ NullPointerException ถูกโยนทิ้ง รหัสการโทรจะทำให้มีความรู้สึกเหมือนกับ NPE เหมือนกับ IllegalArgumentException แต่จะง่ายขึ้นสำหรับนักพัฒนาที่จะทำการดีบักและเข้าใจว่าเกิดอะไรขึ้นถ้า NPE ถูกโยนแทนที่จะเป็นรหัสของคุณที่พยายามดำเนินการบางอย่างที่ไม่คาดคิดอื่น ๆ ตรรกะ - ซึ่งส่งผลให้แอปพลิเคชันล้มเหลวในที่สุด


2
ควรใช้การยืนยันที่ดีกว่าเช่น Contract.notNull (abc "abc ต้องไม่ใช่ค่าว่างเปล่ามันโหลดไม่สำเร็จในระหว่าง xyz หรือไม่") - นี่เป็นวิธีที่มีขนาดกะทัดรัดกว่าการทำ if (abc! = null) {โยน RuntimeException ใหม่ ... }
ianpojman

76

เฟรมเวิร์กคอลเล็กชันของ Google นำเสนอวิธีที่ดีและสง่างามในการตรวจสอบ null

มีวิธีการในชั้นเรียนห้องสมุดเช่นนี้:

static <T> T checkNotNull(T e) {
   if (e == null) {
      throw new NullPointerException();
   }
   return e;
}

และการใช้งาน (ด้วยimport static):

...
void foo(int a, Person p) {
   if (checkNotNull(p).getAge() > a) {
      ...
   }
   else {
      ...
   }
}
...

หรือในตัวอย่างของคุณ:

checkNotNull(someobject).doCalc();

71
mmm ความแตกต่างคืออะไร? p.getAge () จะโยน NPE เดียวกันโดยมีค่าใช้จ่ายน้อยลงและติดตามสแต็กที่ชัดเจนยิ่งขึ้น ฉันพลาดอะไรไป
mysomic

15
เป็นการดีกว่าที่จะโยน IllegalArgumentException ("e == null") ในตัวอย่างของคุณเนื่องจากมันบ่งชี้อย่างชัดเจนว่าเป็นข้อยกเว้นที่โปรแกรมเมอร์ตั้งใจ (พร้อมด้วยข้อมูลเพียงพอที่จะอนุญาตให้ผู้ดูแลระบุปัญหา) NullPointerExceptions ควรจะสงวนไว้สำหรับ JVM เป็นมันแล้วบ่งชี้อย่างชัดเจนว่านี่คือไม่ได้ตั้งใจ (และมักจะเกิดขึ้นที่ใดที่หนึ่งยากที่จะระบุ)
Thorbjørn Ravn Andersen

6
นี่เป็นส่วนหนึ่งของ Google Guava
Steven Benitez

32
มีกลิ่นเหมือนวิศวกรรมมากเกินไปสำหรับฉัน เพียงปล่อยให้ JVM ขว้าง NPE และไม่เกะกะรหัสของคุณด้วยขยะนี้
Alex Worden

5
ฉันชอบและเปิดวิธีการและคอนสตรัคเตอร์ส่วนใหญ่ด้วยการตรวจสอบข้อโต้แย้งอย่างชัดเจน หากมีข้อผิดพลาดวิธีการมักจะล้มเหลวในสองสามบรรทัดแรกและฉันรู้ว่าการอ้างอิงที่กระทำผิดกฎหมายโดยไม่ต้องค้นหาอะไรgetThing().getItsThing().getOtherThing().wowEncapsulationIsBroken().setLol("hi");
Cory Kendall

75

บางครั้งคุณมีวิธีการที่ใช้กับพารามิเตอร์ที่กำหนดการดำเนินการแบบสมมาตร:

a.f(b); <-> b.f(a);

ถ้าคุณรู้ว่าขไม่มีทางเป็นโมฆะคุณสามารถสลับมันได้ มันจะมีประโยชน์มากที่สุดสำหรับเท่ากับ: แทนที่จะดีกว่าทำfoo.equals("bar");"bar".equals(foo);


2
แต่คุณต้องสมมติequals(อาจเป็นวิธีใดก็ได้) จะจัดการกับค่า Null ได้อย่างถูกต้อง การทำเช่นนี้จริงๆคือการส่งผ่านความรับผิดชอบไปให้คนอื่น (หรือวิธีอื่น)
Supericy

4
@ Supercy โดยทั่วไปใช่ แต่equals(หรือวิธีการใด ๆ ) มีการตรวจสอบnullต่อไป หรือรัฐอย่างชัดเจนว่ามันไม่ได้
Angelo Fuchs

74

แทนที่จะใช้รูปแบบวัตถุ Null ซึ่งมีประโยชน์ - คุณอาจพิจารณาสถานการณ์ที่วัตถุ null เป็นจุดบกพร่อง

เมื่อข้อยกเว้นถูกส่งไปตรวจสอบการติดตามสแต็กและทำงานผ่านบั๊ก


17
ปัญหาคือโดยปกติแล้วคุณจะหลวมบริบทเนื่องจาก NullPointerException ไม่ได้ระบุว่าตัวแปรใดเป็นโมฆะและคุณอาจมีหลาย "." - การดำเนินการในบรรทัด การใช้ "if (foo == null) ให้เปิดใช้งานใหม่ RuntimeException (" foo == null ")" ช่วยให้คุณระบุสิ่งที่ผิดได้อย่างชัดเจนทำให้กองติดตามของคุณมีค่ามากขึ้นสำหรับผู้ที่ต้องแก้ไข
Thorbjørn Ravn Andersen

1
ด้วย Andersen - ฉันจะรักระบบการยกเว้นของ Java เพื่อให้สามารถรวมชื่อของตัวแปรที่กำลังทำงานอยู่ดังนั้น NullPointerExceptions จะไม่เพียง แต่บ่งบอกถึงบรรทัดที่มีข้อยกเว้นเกิดขึ้น แต่ยังรวมถึงชื่อตัวแปรด้วย สิ่งนี้ควรใช้งานได้ดีกับซอฟต์แวร์ที่ไม่ได้ทำการสแกน
fwielstra

ฉันยังไม่ได้จัดการเพื่อให้มันทำงานได้ แต่นี่มีจุดประสงค์เพื่อแก้ไขปัญหาดังกล่าว
MatrixFrog

อะไรคือปัญหา? เพียงแค่จับ NPE ในระดับที่เหมาะสมซึ่งมีบริบทเพียงพอให้ถ่ายโอนข้อมูลบริบทแล้วสร้างข้อยกเว้นใหม่ ... มันง่ายมากที่มี Java
user1050755

3
ฉันมีศาสตราจารย์คนหนึ่งที่เทศนาต่อต้านการเรียกวิธีการ ทฤษฎีของเขาคือคุณควรระวังโซ่สายที่ยาวกว่า 2 วิธี ฉันไม่ทราบว่าเป็นกฎที่ยากหรือไม่ แต่ก็เป็นการลบปัญหาส่วนใหญ่ด้วยการติดตามสแต็กของ NPE อย่างแน่นอน
RustyTheBoyRobot

74

Null ไม่ใช่ 'ปัญหา' มันเป็นส่วนสำคัญของชุดเครื่องมือสร้างแบบจำลองที่สมบูรณ์ ซอฟต์แวร์มีจุดมุ่งหมายเพื่อสร้างแบบจำลองความซับซ้อนของโลกและเป็นศูนย์รับภาระ Null ระบุว่า 'ไม่มีข้อมูล' หรือ 'ไม่รู้จัก'ใน Java และสิ่งที่คล้ายกัน ดังนั้นจึงเหมาะสมที่จะใช้ค่า Null เพื่อวัตถุประสงค์เหล่านี้ ฉันไม่ชอบรูปแบบ 'Null object' ฉันคิดว่ามันทำให้เกิด ' ผู้ที่จะปกป้องผู้พิทักษ์ '
หากคุณถามฉันชื่อแฟนของฉันคืออะไรฉันจะบอกคุณว่าฉันไม่มีแฟน ในภาษาจาวาฉันจะคืนค่า null อีกทางเลือกหนึ่งคือการโยนข้อยกเว้นที่มีความหมายเพื่อระบุปัญหาที่ไม่สามารถ (หรือไม่ ')

  1. สำหรับ 'คำถามที่ไม่รู้จัก' ให้ 'คำตอบที่ไม่รู้จัก' (ปลอดภัยต่อศูนย์ซึ่งเป็นสิ่งที่ถูกต้องจากมุมมองทางธุรกิจ) การตรวจสอบข้อโต้แย้งสำหรับ null หนึ่งครั้งในวิธีหนึ่งก่อนการใช้งานจะช่วยลดผู้โทรหลายคนจากการตรวจสอบก่อนที่จะมีการโทร

    public Photo getPhotoOfThePerson(Person person) {
        if (person == null)
            return null;
        // Grabbing some resources or intensive calculation
        // using person object anyhow.
    }

    ก่อนหน้านี้นำไปสู่การไหลของลอจิกปกติเพื่อไม่ให้มีภาพของแฟนสาวที่ไม่มีอยู่จริงจากห้องสมุดภาพถ่ายของฉัน

    getPhotoOfThePerson(me.getGirlfriend())

    และเหมาะกับ Java API ใหม่ (รอคอย)

    getPhotoByName(me.getGirlfriend()?.getName())

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

    public static MyEnum parseMyEnum(String value); // throws IllegalArgumentException
    public static MyEnum parseMyEnumOrNull(String value);

    และอย่าเกลียดที่จะพิมพ์<alt> + <shift> + <j>(สร้าง javadoc ใน Eclipse) และเขียนคำเพิ่มเติมสามคำสำหรับ API สาธารณะของคุณ สิ่งนี้จะเพียงพอสำหรับทุกคนยกเว้นผู้ที่ไม่ได้อ่านเอกสาร

    /**
     * @return photo or null
     */

    หรือ

    /**
     * @return photo, never null
     */
  2. นี้ค่อนข้างกรณีทฤษฎีและในกรณีส่วนใหญ่คุณควรต้องการ Java API null ปลอดภัย (ในกรณีที่มันจะได้รับการปล่อยตัวในอีก 10 ปี) แต่เป็นคลาสย่อยของNullPointerException Exceptionดังนั้นจึงเป็นรูปแบบThrowableที่บ่งบอกถึงเงื่อนไขว่าแอปพลิเคชันที่สมเหตุสมผลอาจต้องการที่จะจับ ( javadoc )! ในการใช้ประโยชน์แรกมากที่สุดของข้อยกเว้นและแยกต่างหากจัดการข้อผิดพลาดรหัสจากโค้ด 'ปกติ' ( ตามที่ผู้สร้างของ Java ) NullPointerExceptionมันมีความเหมาะสมเป็นสำหรับฉันที่จะจับ

    public Photo getGirlfriendPhoto() {
        try {
            return appContext.getPhotoDataSource().getPhotoByName(me.getGirlfriend().getName());
        } catch (NullPointerException e) {
            return null;
        }
    }

    คำถามอาจเกิดขึ้น:

    Q. จะเกิดอะไรขึ้นถ้าgetPhotoDataSource()ส่งคืน null
    A. มันขึ้นอยู่กับตรรกะทางธุรกิจ หากฉันไม่พบอัลบั้มรูปฉันจะไม่แสดงรูปให้คุณเห็น จะเกิดอะไรขึ้นถ้า appContext ไม่เริ่มต้น ตรรกะทางธุรกิจของวิธีการนี้สอดคล้องกับสิ่งนี้ หากตรรกะเดียวกันควรเข้มงวดมากขึ้นให้ส่งข้อยกเว้นเป็นส่วนหนึ่งของตรรกะทางธุรกิจและควรใช้การตรวจสอบความว่างเปล่า (กรณีที่ 3) ใหม่ Java Null ปลอดภัยเหมาะกับ API ที่ดีกว่าที่นี่เพื่อระบุการคัดเลือกสิ่งที่หมายถึงและสิ่งที่ไม่ได้หมายความว่าจะต้องเริ่มต้นที่จะล้มเหลวได้อย่างรวดเร็วในกรณีของข้อผิดพลาดโปรแกรมเมอร์

    Q. สามารถเรียกใช้รหัสซ้ำซ้อนและสามารถคว้าทรัพยากรที่ไม่จำเป็นออกได้
    ตอบอาจเกิดขึ้นได้หากgetPhotoByName()พยายามเปิดการเชื่อมต่อฐานข้อมูลสร้างPreparedStatementและใช้ชื่อบุคคลเป็นพารามิเตอร์ SQL ในที่สุด วิธีการสำหรับคำถามที่ไม่รู้จักให้คำตอบที่ไม่รู้จัก (กรณีที่ 1) ทำงานที่นี่ ก่อนที่จะทำการรวบรวมทรัพยากรวิธีควรตรวจสอบพารามิเตอร์และส่งคืนผลลัพธ์ 'ไม่ทราบ' หากจำเป็น

    ถามวิธีการนี้มีโทษประสิทธิภาพเนื่องจากมีการเปิดปิดทดลอง
    A. ซอฟต์แวร์ควรเข้าใจและแก้ไขได้ง่ายในตอนแรก หลังจากนี้จะมีใครคิดถึงการแสดงและถ้าจำเป็น! และในกรณีที่จำเป็น! ( แหล่งที่มา ) และอื่น ๆ อีกมากมาย)

    PS วิธีนี้จะมีเหตุผลที่จะใช้เป็นรหัสการจัดการข้อผิดพลาดแยกต่างหากจากหลักการรหัส "ปกติ" มีเหตุผลที่จะใช้ในบางสถานที่ ลองพิจารณาตัวอย่างต่อไป:

    public SomeValue calculateSomeValueUsingSophisticatedLogic(Predicate predicate) {
        try {
            Result1 result1 = performSomeCalculation(predicate);
            Result2 result2 = performSomeOtherCalculation(result1.getSomeProperty());
            Result3 result3 = performThirdCalculation(result2.getSomeProperty());
            Result4 result4 = performLastCalculation(result3.getSomeProperty());
            return result4.getSomeProperty();
        } catch (NullPointerException e) {
            return null;
        }
    }
    
    public SomeValue calculateSomeValueUsingSophisticatedLogic(Predicate predicate) {
        SomeValue result = null;
        if (predicate != null) {
            Result1 result1 = performSomeCalculation(predicate);
            if (result1 != null && result1.getSomeProperty() != null) {
                Result2 result2 = performSomeOtherCalculation(result1.getSomeProperty());
                if (result2 != null && result2.getSomeProperty() != null) {
                    Result3 result3 = performThirdCalculation(result2.getSomeProperty());
                    if (result3 != null && result3.getSomeProperty() != null) {
                        Result4 result4 = performLastCalculation(result3.getSomeProperty());
                        if (result4 != null) {
                            result = result4.getSomeProperty();
                        }
                    }
                }
            }
        }
        return result;
    }

    PPS สำหรับผู้ที่ลงคะแนนอย่างรวดเร็ว (และไม่เร็วในการอ่านเอกสาร) ฉันอยากจะบอกว่าฉันไม่เคยพบข้อยกเว้นตัวชี้โมฆะ (NPE) ในชีวิตของฉัน แต่เป็นไปได้นี้ได้รับการออกแบบโดยเจตนาโดยผู้สร้าง Java เพราะ NPE เป็น subclass Exceptionของ เรามีแบบอย่างในประวัติศาสตร์ Java เมื่อไม่ได้ThreadDeathเป็นErrorเพราะมันเป็นข้อผิดพลาดของแอปพลิเคชัน แต่เพียงอย่างเดียวเพราะมันไม่ได้ตั้งใจที่จะถูกจับ! NPE เหมาะกับการเป็นErrorมากกว่าเท่าไหร่ThreadDeath! แต่มันไม่ใช่

  3. ตรวจสอบ 'ไม่มีข้อมูล' หากตรรกะทางธุรกิจบอกเป็นนัย

    public void updatePersonPhoneNumber(Long personId, String phoneNumber) {
        if (personId == null)
            return;
        DataSource dataSource = appContext.getStuffDataSource();
        Person person = dataSource.getPersonById(personId);
        if (person != null) {
            person.setPhoneNumber(phoneNumber);
            dataSource.updatePerson(person);
        } else {
            Person = new Person(personId);
            person.setPhoneNumber(phoneNumber);
            dataSource.insertPerson(person);
        }
    }

    และ

    public void updatePersonPhoneNumber(Long personId, String phoneNumber) {
        if (personId == null)
            return;
        DataSource dataSource = appContext.getStuffDataSource();
        Person person = dataSource.getPersonById(personId);
        if (person == null)
            throw new SomeReasonableUserException("What are you thinking about ???");
        person.setPhoneNumber(phoneNumber);
        dataSource.updatePerson(person);
    }

    หาก appContext หรือ dataSource ไม่ได้เริ่มต้นรันไทม์ที่ไม่ได้จัดการ NullPointerException จะฆ่าเธรดปัจจุบันและจะถูกประมวลผลโดยThread.defaultUncaughtExceptionHandler (สำหรับคุณในการกำหนดและใช้ตัวบันทึกรายการโปรด หากไม่ได้ตั้งค่าThreadGroup # uncaughtExceptionจะพิมพ์ stacktrace ไปที่ระบบ err หนึ่งควรตรวจสอบบันทึกข้อผิดพลาดของแอปพลิเคชันและเปิดปัญหา Jira สำหรับข้อยกเว้นที่ไม่ได้จัดการแต่ละอันซึ่งอันที่จริงแล้วคือข้อผิดพลาดของแอปพลิเคชัน โปรแกรมเมอร์ควรแก้ไขข้อผิดพลาดบางอย่างในสิ่งที่เริ่มต้น


6
การจับNullPointerExceptionและกลับมาnullเป็นเรื่องที่น่ากลัวในการแก้ไขข้อบกพร่อง คุณจะจบลงด้วย NPE ในภายหลังและเป็นการยากที่จะเข้าใจว่าเดิมทีเป็นโมฆะ
artbristol

23
ฉันจะลงคะแนนถ้าฉันมีชื่อเสียง ไม่เพียง แต่เป็นโมฆะไม่จำเป็นมันเป็นรูในระบบพิมพ์ การกำหนด Tree ให้กับรายการเป็นข้อผิดพลาดประเภทเนื่องจาก tree ไม่ใช่ค่าของรายการชนิด โดยตรรกะเดียวกันนั้นการกำหนดค่า Null ควรเป็นข้อผิดพลาดประเภทเนื่องจากค่า Null ไม่ใช่ค่าของชนิดวัตถุหรือชนิดที่มีประโยชน์สำหรับเรื่องนั้น แม้แต่คนที่ประดิษฐ์โมฆะก็คิดว่ามันเป็น "ความผิดพลาดพันล้านดอลลาร์" ของเขา แนวคิดของ "ค่าที่อาจเป็นค่าของ T หรืออะไร" เป็นประเภทของตัวเองและควรจะแสดงเช่น (เช่นอาจ <T> หรือตัวเลือก <T>)
Doval

2
ในฐานะของ "บางที <T> หรือตัวเลือก <T>" คุณยังคงต้องเขียนโค้ดเช่นif (maybeNull.hasValue()) {...}นั้นแตกต่างกันif (maybeNull != null)) {...}อย่างไร
Mykhaylo Adamovych

1
ในฐานะของ "การจับ NullPointerException และการคืนค่า Null เป็นสิ่งที่น่ากลัวในการดีบักคุณจะพบกับ NPE ในภายหลังและเป็นเรื่องยากที่จะเข้าใจว่าสิ่งใดที่เดิมเป็นโมฆะ" ฉันเห็นด้วยอย่างยิ่ง! ในกรณีดังกล่าวคุณควรเขียนคำสั่ง 'if' หรือโยน NPE โหลหากตรรกะทางธุรกิจแสดงถึงข้อมูลในสถานที่หรือใช้โอเปอเรเตอร์ที่ปลอดภัยจาก null จาก Java ใหม่ แต่มีบางกรณีที่ฉันไม่สนใจว่าขั้นตอนที่แน่นอนจะให้ฉันว่างเปล่า ตัวอย่างเช่นการคำนวณค่าบางอย่างสำหรับผู้ใช้ก่อนที่จะแสดงบนหน้าจอเมื่อคุณคาดว่าข้อมูลอาจหายไป
Mykhaylo Adamovych

3
@ MykhayloAdamovych: ประโยชน์ของMaybe<T>หรือOptional<T>ไม่อยู่ในกรณีที่คุณTอาจเป็นโมฆะ แต่ในกรณีที่ไม่ควรเป็นโมฆะ หากคุณมีประเภทที่ชัดเจนว่า "ค่านี้อาจเป็นโมฆะ - ใช้ด้วยความระมัดระวัง" และคุณใช้และส่งกลับประเภทดังกล่าวอย่างสม่ำเสมอจากนั้นเมื่อใดก็ตามที่คุณเห็นข้อความเก่า ๆTในรหัสของคุณคุณสามารถถือว่าไม่มีวันว่าง (หลักสูตรนี้จะเป็นประโยชน์มากขึ้นถ้าใช้บังคับโดยรวบรวม.)
เจ้าพระยา

71

Java 7 มีjava.util.Objectsคลาสยูทิลิตี้ใหม่ซึ่งมีrequireNonNull()วิธีการ ทั้งหมดนี้ก็คือการโยนNullPointerExceptionถ้าอาร์กิวเมนต์ของมันเป็นโมฆะ แต่มันทำความสะอาดโค้ดเล็กน้อย ตัวอย่าง:

Objects.requireNonNull(someObject);
someObject.doCalc();

วิธีนี้มีประโยชน์มากที่สุดสำหรับการตรวจสอบก่อนการกำหนดใน Constructor ซึ่งการใช้งานแต่ละครั้งสามารถบันทึกรหัสสามบรรทัด:

Parent(Child child) {
   if (child == null) {
      throw new NullPointerException("child");
   }
   this.child = child;
}

กลายเป็น

Parent(Child child) {
   this.child = Objects.requireNonNull(child, "child");
}

9
อันที่จริงตัวอย่างของคุณประกอบด้วยการขยายตัวของโค้ด: บรรทัดแรกนั้นไม่จำเป็นเพราะ NPE จะถูกโยนทิ้งในบรรทัดที่สอง ;-)
user1050755

1
จริง doCalc(someObject)เป็นตัวอย่างที่ดีกว่าที่จะเป็นถ้าบรรทัดที่สองเป็น
Stuart Marks

ขึ้นอยู่กับ หากคุณเป็นผู้เขียน doCalc () ฉันขอแนะนำให้นำเช็คไปไว้ในเนื้อหาของวิธีการนั้น (ถ้าเป็นไปได้) จากนั้นคุณมักจะเรียก someObject.someMethod () โดยที่ไม่จำเป็นต้องตรวจสอบ null อีกครั้ง :-)
user1050755

ถ้าคุณไม่ใช่ผู้แต่งdoCalc()และมันไม่ได้โยน NPE ทันทีเมื่อได้รับ null คุณจะต้องตรวจสอบ null และโยน NPE ด้วยตัวคุณเอง นั่นคือสิ่งที่Objects.requireNonNull()มีไว้เพื่อ
Stuart Marks

7
มันไม่ใช่แค่การขยายโค้ด ดีกว่าที่จะตรวจสอบล่วงหน้ากว่าครึ่งทางผ่านวิธีการที่ทำให้เกิดผลข้างเคียงหรือใช้เวลา / พื้นที่
Rob Grant

51

ในที่สุดวิธีเดียวที่จะแก้ปัญหานี้ได้อย่างสมบูรณ์คือการใช้ภาษาการเขียนโปรแกรมที่แตกต่างกัน:

  • ใน Objective-C คุณสามารถทำสิ่งที่เทียบเท่ากับการเรียกใช้เมธอดnilและไม่มีอะไรเกิดขึ้นแน่นอน สิ่งนี้ทำให้การตรวจสอบโมฆะส่วนใหญ่ไม่จำเป็น แต่สามารถทำให้เกิดข้อผิดพลาดได้ยากขึ้นในการวินิจฉัย
  • ในNiceภาษาที่ได้รับจาก Java มีทั้งหมดสองรุ่นคือรุ่นที่อาจเป็นโมฆะและรุ่นที่ไม่เป็นโมฆะ คุณสามารถเรียกใช้เมธอดในประเภทที่ไม่ใช่ค่าว่างเท่านั้น ประเภทที่อาจเป็นโมฆะสามารถแปลงเป็นประเภทที่ไม่เป็นโมฆะผ่านการตรวจสอบอย่างชัดเจนสำหรับโมฆะ สิ่งนี้ทำให้ง่ายต่อการทราบว่าการตรวจสอบค่าศูนย์เป็นสิ่งจำเป็นหรือไม่

ว้าว ... คำตอบที่ถูกต้องที่สุดและถูกลดระดับลงความยุติธรรมอยู่ที่ไหน? ใน Java null จะเป็นค่าที่ถูกต้องเสมอ มันเป็นข้อพิสูจน์สำหรับทุก ๆ อย่างว่าเป็นวัตถุ - Null เป็นทุกอย่าง (แน่นอนว่าเราเพิกเฉยต่อสิ่งดั้งเดิมที่นี่ แต่คุณได้รับความคิด) โดยส่วนตัวแล้วฉันชอบวิธีการของ Nice แต่เราสามารถทำได้เพื่อให้สามารถเรียกใช้เมธอดที่เป็นโมฆะชนิดและเลื่อนระดับ NPE ไปเป็นข้อยกเว้นที่ตรวจสอบได้ สิ่งนี้จะต้องทำผ่านสวิตช์คอมไพเลอร์แม้ว่ามันจะทำลายรหัสที่มีอยู่ทั้งหมด :(
CurtainDog

4
ฉันไม่คุ้นเคยกับ Nice แต่ Kotlin ใช้ความคิดเดียวกันมีประเภท nullable และ non-null เข้าสู่ระบบชนิดของภาษา รัดกุมมากกว่า Optionals หรือรูปแบบวัตถุ null
mtsahakis

38

"ปัญหา" ทั่วไปใน Java แน่นอน

ครั้งแรกที่ฉันคิดเกี่ยวกับเรื่องนี้:

ฉันคิดว่ามันไม่ดีที่จะ "กิน" บางสิ่งเมื่อ NULL ผ่านไปโดยที่ NULL ไม่ใช่ค่าที่ถูกต้อง หากคุณไม่ได้ออกจากวิธีการที่มีข้อผิดพลาดบางประเภทหมายความว่าไม่มีอะไรผิดพลาดในวิธีการของคุณซึ่งไม่เป็นความจริง จากนั้นคุณอาจกลับมาเป็นโมฆะในกรณีนี้และในวิธีการรับคุณตรวจสอบอีกครั้งเป็นโมฆะและมันไม่สิ้นสุดและคุณจะจบลงด้วย "if! = null" ฯลฯ

ดังนั้น IMHO, null จะต้องเป็นข้อผิดพลาดที่สำคัญซึ่งจะป้องกันไม่ให้มีการดำเนินการเพิ่มเติม (นั่นคือโดยที่ null ไม่ได้เป็นค่าที่ถูกต้อง)

วิธีที่ฉันแก้ปัญหานี้คือ:

ก่อนอื่นฉันปฏิบัติตามอนุสัญญานี้:

  1. เมธอดสาธารณะทั้งหมด / API ตรวจสอบข้อโต้แย้งของตนว่าเป็นค่าว่างเสมอ
  2. วิธีการส่วนตัวทั้งหมดไม่ตรวจสอบ null เนื่องจากเป็นวิธีการควบคุม (ให้ตายด้วยข้อยกเว้น nullpointer ในกรณีที่ไม่ได้จัดการด้านบน)
  3. วิธีอื่น ๆ เท่านั้นที่ไม่ได้ตรวจสอบว่าเป็นโมฆะคือวิธีการอรรถประโยชน์ พวกเขาเป็นสาธารณะ แต่ถ้าคุณเรียกพวกเขาด้วยเหตุผลบางอย่างคุณรู้ว่าคุณผ่านพารามิเตอร์อะไร นี่เป็นเหมือนการพยายามต้มน้ำในกาต้มน้ำโดยไม่ให้น้ำ ...

และสุดท้ายในโค้ดบรรทัดแรกของวิธีสาธารณะจะเป็นดังนี้:

ValidationUtils.getNullValidator().addParam(plans, "plans").addParam(persons, "persons").validate();

โปรดทราบว่า addParam () คืนค่าตัวเองเพื่อให้คุณสามารถเพิ่มพารามิเตอร์เพิ่มเติมเพื่อตรวจสอบ

เมธอดvalidate()จะส่งการตรวจสอบValidationExceptionหากพารามิเตอร์ใด ๆ เป็นโมฆะ (ทำเครื่องหมายหรือไม่เลือกเป็นปัญหาด้านการออกแบบ / รสนิยมมากกว่า แต่ValidationExceptionมีการตรวจสอบแล้ว)

void validate() throws ValidationException;

ข้อความจะมีข้อความต่อไปนี้หากตัวอย่างเช่น "plans" เป็นโมฆะ:

" พบค่าอาร์กิวเมนต์ไม่ถูกต้องเป็นค่าว่างสำหรับพารามิเตอร์ [แผน] "

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

และใช่เรารู้ว่านอกเหนือจากบรรทัดนี้เราจะไม่พบค่าว่างอีกต่อไปดังนั้นเราจึงเรียกใช้วิธีการอย่างปลอดภัยกับวัตถุเหล่านั้น

วิธีนี้รหัสจะสะอาดง่ายต่อการดูแลและอ่านได้


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

35

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

  1. อนุญาตให้ข้อยกเว้นกระเพื่อม - จับพวกมันที่ 'ลูปหลัก' หรือในรูทีนการจัดการอื่น ๆ

    • ตรวจสอบเงื่อนไขข้อผิดพลาดและจัดการอย่างเหมาะสม

แน่นอนว่าจะต้องดูที่การเขียนโปรแกรม Aspect Oriented ด้วย - พวกเขามีวิธีที่เรียบร้อยในการแทรกif( o == null ) handleNull()ลงในไบต์ของคุณ


35

นอกเหนือจากการใช้assertคุณสามารถใช้สิ่งต่อไปนี้:

if (someobject == null) {
    // Handle null here then move on.
}

นี่เป็นสิ่งที่ดีกว่า:

if (someobject != null) {
    .....
    .....



    .....
}

2
ทำไมล่ะ? ได้โปรดอย่ารู้สึกว่าถูกป้องกันฉันต้องการเรียนรู้เพิ่มเติมเกี่ยวกับ Java :)
Matthias Meid

9
@Mudu ตามกฎทั่วไปฉันชอบการแสดงออกในคำสั่ง if เป็นคำสั่ง "บวก" มากกว่าที่จะเป็น "ลบ" ดังนั้นหากฉันเห็นว่าif (!something) { x(); } else { y(); }ฉันมีแนวโน้มที่จะสร้างมันขึ้นมาใหม่if (something) { y(); } else { x(); }(แม้ว่าบางคนอาจโต้แย้งว่า!= nullเป็นตัวเลือกที่เป็นบวกมากกว่า ... ) แต่ที่สำคัญกว่านั้นส่วนที่สำคัญของโค้ดไม่ได้ถูกห่อไว้ข้างใน{}และคุณมีการเยื้องน้อยลงหนึ่งระดับสำหรับวิธีการส่วนใหญ่ ฉันไม่รู้ว่านั่นเป็นเหตุผลของ fastcodejava หรือเปล่า แต่นั่นจะเป็นของฉัน
MatrixFrog

1
นี่คือสิ่งที่ฉันมักจะทำเช่นกัน .. รักษาความสะอาดโค้ดในหนังสือของฉัน
Koray Tugay

34

ไม่ใช้ null เลย ไม่อนุญาต

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

เมื่อฉันใช้การฝึกฝนนี้ฉันสังเกตว่าปัญหาดูเหมือนจะแก้ไขได้เอง คุณจะจับสิ่งต่าง ๆ ได้เร็วขึ้นในกระบวนการพัฒนาโดยไม่ได้ตั้งใจและตระหนักว่าคุณมีจุดอ่อน .. และที่สำคัญ .. มันช่วยสรุปความกังวลของโมดูลต่าง ๆ โมดูลต่าง ๆ สามารถ 'เชื่อใจ' ซึ่งกันและกันและไม่ทิ้งขยะ รหัสด้วยการif = null elseสร้าง!

นี่คือการตั้งโปรแกรมป้องกันและส่งผลให้รหัสที่ชัดเจนมากขึ้นในระยะยาว ฆ่าเชื้อโรคข้อมูลเช่นที่นี่โดยการบังคับใช้มาตรฐานที่เข้มงวดและปัญหาหายไป

class C {
    private final MyType mustBeSet;
    public C(MyType mything) {
       mustBeSet=Contract.notNull(mything);
    }
   private String name = "<unknown>";
   public void setName(String s) {
      name = Contract.notNull(s);
   }
}


class Contract {
    public static <T> T notNull(T t) { if (t == null) { throw new ContractException("argument must be non-null"); return t; }
}

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


ทำไมถึงต้องถูก downvote นี้? ในประสบการณ์ของฉันนี้ไกลกว่าวิธีอื่น ๆ ชอบที่จะรู้ว่าทำไมไม่
ianpojman

ฉันเห็นด้วยวิธีการนี้ป้องกันปัญหาที่เกี่ยวข้องกับ nulls แทนที่จะแก้ไขได้ด้วยการตรวจสอบจุดว่างโค้ด null ทุกที่
ChrisBlom

7
ปัญหาของวิธีนี้คือถ้าไม่ตั้งชื่อมันมีค่า "<unknown>" ซึ่งทำตัวเหมือนค่าที่ตั้งไว้ ตอนนี้สมมติว่าฉันต้องตรวจสอบว่าชื่อไม่เคยตั้ง (ไม่ทราบ) ฉันต้องทำการเปรียบเทียบสตริงกับค่าพิเศษ "<unknown>"
Steve Kuo

1
จุดดีที่แท้จริงของสตีฟ สิ่งที่ฉันมักจะทำคือมีค่าเป็นค่าคงที่เช่นสตริงสาธารณะสุดท้ายคงที่ UNSET = "__ unset" ... ส่วนตัว String field = UNSET ... จากนั้นส่วนตัวบูลีน isSet () {return UNSET.equals (field); }
ianpojman

IMHO นี่คือการใช้งาน Null Object Pattern ด้วยตัวคุณเองในการใช้งานตัวเลือก (สัญญา) มันมีพฤติกรรมอย่างไรในคลาสคลาสที่คงอยู่? ฉันไม่เห็นความเกี่ยวข้องในกรณีดังกล่าว
Kurapika

31

Guava ห้องสมุดแกนที่มีประโยชน์มากโดย Google มี API ที่ดีและมีประโยชน์เพื่อหลีกเลี่ยงค่าว่าง ฉันพบว่าUsingAndAvoidingNull อธิบายมีประโยชน์มาก

ตามที่อธิบายไว้ในวิกิ:

Optional<T>เป็นวิธีในการแทนที่การอ้างอิง T nullable ด้วยค่าที่ไม่เป็นโมฆะ ตัวเลือกอาจประกอบด้วยการอ้างอิง T ที่ไม่เป็นโมฆะ (ในกรณีที่เราบอกว่าการอ้างอิงคือ "ปัจจุบัน") หรืออาจไม่มีอะไรเลย (ในกรณีนี้เราบอกว่าการอ้างอิงนั้นเป็น "ขาด") มันไม่เคยบอกว่า "มีค่าว่าง"

การใช้งาน:

Optional<Integer> possible = Optional.of(5);
possible.isPresent(); // returns true
possible.get(); // returns 5

@CodyGuldner ถูกต้อง Cody ฉันให้ใบเสนอราคาที่เกี่ยวข้องจากลิงก์เพื่อให้บริบทเพิ่มเติม
Murat Derya Özen

25

นี่เป็นปัญหาที่พบบ่อยมากสำหรับนักพัฒนา Java ทุกคน ดังนั้นจึงมีการสนับสนุนอย่างเป็นทางการใน Java 8 เพื่อแก้ไขปัญหาเหล

Java 8 java.util.Optional<T>ได้แนะนำ มันเป็นภาชนะที่อาจหรือไม่อาจเก็บค่าที่ไม่ใช่ศูนย์ Java 8 ได้ให้วิธีที่ปลอดภัยกว่าในการจัดการกับวัตถุที่มีค่าอาจเป็นโมฆะในบางกรณี มันเป็นแรงบันดาลใจจากความคิดของHaskellและสกาล่า

โดยสรุปคลาสทางเลือกมีวิธีการจัดการกรณีและปัญหาที่มีค่าอยู่หรือขาดหายไปอย่างชัดเจน อย่างไรก็ตามข้อดีเมื่อเทียบกับการอ้างอิงว่างคือคลาสตัวเลือก <T> บังคับให้คุณคิดถึงกรณีและปัญหาเมื่อไม่มีค่า ดังนั้นคุณสามารถป้องกันข้อยกเว้นตัวชี้โมฆะที่ไม่ได้ตั้งใจได้

ในตัวอย่างข้างต้นเรามีโรงงานบริการที่บ้านซึ่งส่งคืนการจัดการไปยังอุปกรณ์หลายเครื่องที่มีอยู่ในบ้าน แต่บริการเหล่านี้อาจมีหรือไม่มีให้ใช้งาน มันหมายความว่ามันอาจส่งผลให้ NullPointerException แทนการเพิ่มifเงื่อนไขที่เป็นโมฆะก่อนที่จะใช้บริการใด ๆ ลองห่อในตัวเลือก <Service>

เปลี่ยนเป็นตัวเลือก <T>

ลองพิจารณาวิธีรับการอ้างอิงบริการจากโรงงาน แทนที่จะส่งคืนการอ้างอิงบริการห่อด้วยตัวเลือก ช่วยให้ผู้ใช้ API ทราบว่าบริการที่ส่งคืนอาจมีหรือไม่มีให้ใช้งานหรือใช้งานได้

public Optional<Service> getRefrigertorControl() {
      Service s = new  RefrigeratorService();
       //...
      return Optional.ofNullable(s);
   }

ตามที่คุณเห็นOptional.ofNullable()ให้วิธีง่ายๆในการรับการอ้างอิงแบบห่อ มีวิธีการที่จะได้รับการอ้างอิงของตัวเลือกอื่นเป็นอย่างใดอย่างหนึ่งและOptional.empty() Optional.of()อันที่หนึ่งสำหรับการคืนค่าวัตถุว่างเปล่าแทนการปรับค่า Null และค่าอื่น ๆ เพื่อตัดค่าวัตถุที่ไม่เป็นค่าว่างตามลำดับ

มันจะช่วยให้หลีกเลี่ยงการตรวจสอบค่า NULL ได้อย่างไร?

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

Optional ref = homeServices.getRefrigertorControl();
ref.ifPresent(HomeServices::switchItOn);

ไม่บังคับ. ifPresent จะเรียกใช้ Consumer ที่ระบุพร้อมกับการอ้างอิงหากเป็นค่าที่ไม่ใช่ค่า null มิฉะนั้นจะไม่ทำอะไรเลย

@FunctionalInterface
public interface Consumer<T>

แสดงให้เห็นถึงการดำเนินงานที่รับอาร์กิวเมนต์อินพุตเดียวและส่งกลับไม่มีผล แตกต่างจากอินเทอร์เฟซการทำงานอื่น ๆ ส่วนใหญ่ Consumer คาดว่าจะทำงานผ่านผลข้างเคียง มันสะอาดและง่ายต่อการเข้าใจ ในตัวอย่างโค้ดข้างต้นHomeService.switchOn(Service)ได้รับการเรียกถ้าการอ้างอิงการถือครองไม่จำเป็นเป็นโมฆะ

เราใช้ผู้ประกอบการที่สามบ่อยมากสำหรับการตรวจสอบสภาพเป็นโมฆะและส่งกลับค่าทางเลือกหรือค่าเริ่มต้น ทางเลือกเป็นอีกวิธีหนึ่งในการจัดการสภาพเดียวกันโดยไม่ตรวจสอบค่าว่าง ไม่บังคับ. orElse (defaultObj) จะส่งกลับ defaultObj หากตัวเลือกมีค่า Null ลองใช้สิ่งนี้ในโค้ดตัวอย่างของเรา:

public static Optional<HomeServices> get() {
    service = Optional.of(service.orElse(new HomeServices()));
    return service;
}

ตอนนี้ HomeServices.get () ทำสิ่งเดียวกัน แต่เป็นวิธีที่ดีกว่า มันจะตรวจสอบว่าบริการได้เริ่มต้นแล้วไม่ หากเป็นเช่นนั้นให้ส่งคืนหรือสร้างบริการใหม่ ตัวเลือก <T> .orElse (T) ช่วยในการส่งคืนค่าเริ่มต้น

สุดท้ายนี่คือ NPE ของเราเช่นเดียวกับรหัสการตรวจสอบที่ว่างเปล่า:

import java.util.Optional;
public class HomeServices {
    private static final int NOW = 0;
    private static Optional<HomeServices> service;

public static Optional<HomeServices> get() {
    service = Optional.of(service.orElse(new HomeServices()));
    return service;
}

public Optional<Service> getRefrigertorControl() {
    Service s = new  RefrigeratorService();
    //...
    return Optional.ofNullable(s);
}

public static void main(String[] args) {
    /* Get Home Services handle */
    Optional<HomeServices> homeServices = HomeServices.get();
    if(homeServices != null) {
        Optional<Service> refrigertorControl = homeServices.get().getRefrigertorControl();
        refrigertorControl.ifPresent(HomeServices::switchItOn);
    }
}

public static void switchItOn(Service s){
         //...
    }
}

โพสต์ที่สมบูรณ์คือNPE เช่นเดียวกับรหัสตรวจสอบฟรี Null …จริงเหรอ? .


มีการตรวจสอบ null รหัสข้างต้น - if(homeServices != null) {ซึ่งสามารถเปลี่ยนเป็นhomeServices.ifPresent(h -> //action);
KrishPrabakar

23

ฉันชอบบทความจาก Nat Pryce นี่คือลิงค์:

ในบทความยังมีลิงค์ไปยังที่เก็บ Git สำหรับ Java บางที Type ที่ฉันสนใจ แต่ฉันไม่คิดว่ามันคนเดียวสามารถลดการขยายตัวของรหัสตรวจสอบได้ หลังจากทำการค้นคว้าบนอินเทอร์เน็ตฉันคิดว่า! = null code bloat อาจลดลงได้ส่วนใหญ่จากการออกแบบอย่างระมัดระวัง


Michael Feathers ได้เขียนข้อความสั้น ๆ และน่าสนใจเกี่ยวกับแนวทางที่คุณกล่าวถึง: manuelp.newsblur.com/site/424
ivan.aguirre

21

ฉันได้ลองแล้วNullObjectPatternแต่สำหรับฉันไม่ได้เป็นวิธีที่ดีที่สุดเสมอไป บางครั้งมีเมื่อ "ไม่มีการกระทำ" ไม่เหมาะสม

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

ตอนนี้ถึงคำตอบ:

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

แน่นอนว่าประสบการณ์เป็นวิธีที่ดีกว่าในการทำความเข้าใจและใช้คำแนะนำนี้

Byte!


19

อาจเป็นทางเลือกที่ดีที่สุดสำหรับ Java 8 หรือใหม่กว่าคือการใช้Optionalคลาส

Optional stringToUse = Optional.of("optional is there");
stringToUse.ifPresent(System.out::println);

สิ่งนี้มีประโยชน์อย่างยิ่งสำหรับโซ่ยาวที่มีค่า Null เป็นไปได้ ตัวอย่าง:

Optional<Integer> i = Optional.ofNullable(wsObject.getFoo())
    .map(f -> f.getBar())
    .map(b -> b.getBaz())
    .map(b -> b.getInt());

ตัวอย่างวิธีโยนข้อยกเว้นบน null:

Optional optionalCarNull = Optional.ofNullable(someNull);
optionalCarNull.orElseThrow(IllegalStateException::new);

Java 7 แนะนำObjects.requireNonNullวิธีการที่จะมีประโยชน์เมื่อมีการตรวจสอบสิ่งที่ไม่ใช่ความว่างเปล่า ตัวอย่าง:

String lowerVal = Objects.requireNonNull(someVar, "input cannot be null or empty").toLowerCase();

17

ฉันขอตอบโดยทั่วไปมากกว่านี้ได้ไหม!

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

ดังนั้นจึงไม่มีความแตกต่างระหว่าง:

if(object == null){
   //you called my method badly!

}

หรือ

if(str.length() == 0){
   //you called my method badly again!
}

พวกเขาทั้งสองต้องการตรวจสอบให้แน่ใจว่าเราได้รับพารามิเตอร์ที่ถูกต้องก่อนที่เราจะทำหน้าที่อื่น ๆ

ดังที่กล่าวไว้ในคำตอบอื่น ๆ เพื่อหลีกเลี่ยงปัญหาข้างต้นคุณสามารถทำตามรูปแบบสัญญา โปรดดูhttp://en.wikipedia.org/wiki/Design_by_contract

ที่จะใช้รูปแบบนี้ใน java คุณสามารถใช้คำอธิบายประกอบจาวาหลักเช่นjavax.annotation.NotNullหรือใช้ห้องสมุดที่มีความซับซ้อนมากขึ้นเช่นHibernate ตรวจสอบ

เพียงตัวอย่าง:

getCustomerAccounts(@NotEmpty String customerId,@Size(min = 1) String accountType)

ตอนนี้คุณสามารถพัฒนาฟังก์ชั่นหลักของวิธีการของคุณได้อย่างปลอดภัยโดยไม่จำเป็นต้องตรวจสอบพารามิเตอร์อินพุตพวกเขาป้องกันวิธีการของคุณจากพารามิเตอร์ที่ไม่คาดคิด

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

public class Car {

   @NotNull
   private String manufacturer;

   @NotNull
   @Size(min = 2, max = 14)
   private String licensePlate;

   @Min(2)
   private int seatCount;

   // ...
}

javaxโดยนิยามไม่ใช่ "core Java"
โทมัส

17

ฉันไม่สนใจคำตอบที่แนะนำให้ใช้วัตถุ null ในทุกสถานการณ์ รูปแบบนี้อาจทำให้สัญญาผิดพลาดและฝังปัญหาได้ลึกขึ้นและลึกลงแทนที่จะแก้ปัญหาไม่ได้กล่าวถึงว่าการใช้อย่างไม่เหมาะสมจะสร้างกองรหัสสำเร็จรูปที่จำเป็นต้องบำรุงรักษาในอนาคต

ในความเป็นจริงหากสิ่งที่ส่งคืนจากวิธีการเป็นโมฆะและรหัสการโทรต้องตัดสินใจตามนั้นควรมีการโทรก่อนหน้านี้ที่ช่วยให้มั่นใจรัฐ

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

นอกจากนี้ฉันจะไม่แนะนำให้ใช้รูปแบบนี้ซึ่งประเภทนี้หมายถึงการเป็นตัวแทนประเภทดั้งเดิม - เช่นเอนทิตีทางคณิตศาสตร์ที่ไม่ใช่ scalars: เวกเตอร์, เมทริกซ์, จำนวนเชิงซ้อนและวัตถุ POD (ข้อมูลเก่าแบบธรรมดา) ซึ่งหมายถึงการรักษาสถานะ ในรูปแบบของ Java ชนิดในตัว ในกรณีหลังคุณจะสิ้นสุดการเรียกวิธีการทะเยอทะยานกับผลลัพธ์โดยพลการ ตัวอย่างเช่นเมธอด NullPerson.getName () ควรส่งคืนอย่างไร

ควรพิจารณากรณีดังกล่าวเพื่อหลีกเลี่ยงผลลัพธ์ที่ไร้สาระ


วิธีการแก้ปัญหาด้วย "hasBackground ()" มีหนึ่งข้อเสียเปรียบ - มันไม่ปลอดภัยต่อเธรด หากคุณต้องการเรียกสองวิธีแทนหนึ่งคุณต้องซิงโครไนซ์ลำดับทั้งหมดในสภาพแวดล้อมแบบมัลติเธรด
pkalinow

@pkalinow คุณได้ทำตัวอย่างที่วางแผนไว้แล้วเท่านั้นเพื่อชี้ให้เห็นว่าโซลูชันนี้มีข้อเสียเปรียบ หากรหัสไม่ได้หมายถึงให้ทำงานในแอพพลิเคชั่นแบบมัลติเธรดก็จะไม่มีข้อเสียเปรียบ ฉันสามารถใส่รหัส 90% ของคุณที่ไม่ปลอดภัยกับเธรดได้ เราไม่ได้พูดถึงที่นี่เกี่ยวกับโค้ดนี้เรากำลังพูดถึงรูปแบบการออกแบบ และมัลติเธรดเป็นหัวข้อของตัวเอง
ลุค1985

แน่นอนในแอปพลิเคชั่นเธรดเดียวไม่ใช่ปัญหา ฉันได้รับความคิดเห็นนั้นเพราะบางครั้งมันเป็นปัญหา
pkalinow

@pkalinow หากคุณศึกษาหัวข้อนี้ให้ละเอียดยิ่งขึ้นคุณจะพบว่ารูปแบบการออกแบบ Null Object จะไม่แก้ไขปัญหามัลติเธรด ดังนั้นมันจึงไม่เกี่ยวข้อง และตามจริงแล้วฉันได้พบสถานที่ที่รูปแบบนี้เหมาะสมอย่างยิ่งดังนั้นคำตอบดั้งเดิมของฉันจึงผิดไปเล็กน้อย
ลุค1985

16
  1. อย่าเริ่มต้นตัวแปรเป็นโมฆะ
  2. ถ้าเป็นไปไม่ได้ (1) ให้เริ่มต้นการรวบรวมและอาร์เรย์ทั้งหมดเพื่อทำการรวบรวม / อาร์เรย์ว่าง

การทำเช่นนี้ในรหัสของคุณเองและคุณสามารถหลีกเลี่ยง! = null ตรวจสอบ

การตรวจสอบโมฆะเวลาส่วนใหญ่ดูเหมือนจะป้องกันการวนรอบคอลเลกชันหรืออาร์เรย์ดังนั้นเพียงกำหนดค่าเริ่มต้นให้ว่างคุณไม่จำเป็นต้องตรวจสอบ null

// Bad
ArrayList<String> lemmings;
String[] names;

void checkLemmings() {
    if (lemmings != null) for(lemming: lemmings) {
        // do something
    }
}



// Good
ArrayList<String> lemmings = new ArrayList<String>();
String[] names = {};

void checkLemmings() {
    for(lemming: lemmings) {
        // do something
    }
}

มีค่าใช้จ่ายเล็กน้อยในเรื่องนี้ แต่มันคุ้มค่าสำหรับโค้ดที่สะอาดกว่าและ NullPointerExceptions ที่น้อยลง



3
+1 นี่ฉันเห็นด้วย คุณไม่ควรส่งคืนวัตถุเริ่มต้นครึ่งหนึ่ง รหัสที่เกี่ยวข้องกับ Jaxb และรหัสถั่วนั้นไม่ใช่เรื่องง่ายสำหรับเรื่องนี้ มันเป็นการฝึกฝนที่ไม่ดี คอลเลกชันทั้งหมดควรถูกเตรียมใช้งานและวัตถุทั้งหมดควรมีอยู่ด้วย (นึกคิด) ไม่มีการอ้างอิงเป็นโมฆะ พิจารณาวัตถุที่มีการสะสมอยู่ การตรวจสอบว่าวัตถุนั้นไม่เป็นโมฆะการรวบรวมนั้นไม่เป็นโมฆะและการรวบรวมนั้นไม่มีวัตถุที่เป็นโมฆะนั้นไม่มีเหตุผลและโง่เขลา
ggb667

15

นี่เป็นข้อผิดพลาดทั่วไปที่เกิดขึ้นกับนักพัฒนาส่วนใหญ่

เรามีหลายวิธีในการจัดการกับสิ่งนี้

วิธีที่ 1:

org.apache.commons.lang.Validate //using apache framework

notNull (วัตถุวัตถุข้อความสตริง)

วิธีที่ 2:

if(someObject!=null){ // simply checking against null
}

วิธีที่ 3:

@isNull @Nullable  // using annotation based validation

วิธีที่ 4:

// by writing static method and calling it across whereever we needed to check the validation

static <T> T isNull(someObject e){  
   if(e == null){
      throw new NullPointerException();
   }
   return e;
}

การโฆษณา 4. ไม่มีประโยชน์มาก - เมื่อคุณตรวจสอบว่าตัวชี้เป็นโมฆะคุณอาจต้องการเรียกใช้เมธอดนั้น การเรียกใช้เมธอด null จะทำให้คุณมีพฤติกรรมเหมือนกันคือ NullPointerException
pkalinow

11
public static <T> T ifNull(T toCheck, T ifNull) {
    if (toCheck == null) {
           return ifNull;
    }
    return toCheck;
}

2
มีอะไรผิดปกติกับวิธีนี้ฉันคิดว่า @tltester เพียงแค่ต้องการที่จะให้ค่าเริ่มต้นถ้ามันเป็นโมฆะซึ่งทำให้รู้สึก
Sawyer

1
มีวิธีการดังกล่าวใน Apache Commons-lang ObjectUtils.defaultIfNull()คือ: มีอยู่คนหนึ่งที่กว้างขึ้น: ObjectUtils.firstNonNull()ซึ่งสามารถใช้ในการดำเนินกลยุทธ์การย่อยสลาย:firstNonNull(bestChoice, secondBest, thirdBest, fallBack);
ivant
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.