กรองค่าเฉพาะในกรณีที่ไม่ใช่ค่า null โดยใช้แลมบ์ดาใน Java8


160

carฉันมีรายชื่อของวัตถุที่พูด ฉันต้องการที่จะกรองรายการนี้ขึ้นอยู่กับพารามิเตอร์บางโดยใช้ Java 8. แต่ถ้าพารามิเตอร์เป็นมันจะพ่นnull NullPointerExceptionจะกรองค่า Null ได้อย่างไร?

รหัสปัจจุบันมีดังนี้

requiredCars = cars.stream().filter(c -> c.getName().startsWith("M"));

นี้พ่นNullPointerExceptionถ้าผลตอบแทนgetName()null


คุณต้องการที่จะ "กรองค่าเฉพาะในกรณีที่ไม่เป็นโมฆะ" หรือ "กรองค่า null"? นั่นฟังดูขัดแย้งกับฉัน
Holger

3
ฉันขอแนะนำให้คุณยอมรับคำตอบของ Tunakiเพราะดูเหมือนจะเป็นคำตอบเดียวที่ตอบคำถามของคุณ
Mark Booth

คำตอบ:


322

ในตัวอย่างนี้ฉันคิดว่า @Tagir ถูกต้อง 100% นำไปไว้ในตัวกรองเดียวและทำการตรวจสอบสองครั้ง ฉันจะไม่ใช้Optional.ofNullableสิ่งที่เป็นตัวเลือกสำหรับประเภทการส่งคืนไม่ใช่เพื่อทำตรรกะ ... แต่จริงๆแล้วไม่ได้อยู่ที่นี่หรือที่นั่น

ฉันต้องการชี้ให้เห็นว่าjava.util.Objectsมีวิธีการที่ดีสำหรับกรณีนี้ในวงกว้างดังนั้นคุณสามารถทำได้:

cars.stream()
    .filter(Objects::nonNull)

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

cars.stream()
    .filter(car -> Objects.nonNull(car))

หากต้องการตอบคำถามในมือบางส่วนเพื่อส่งคืนรายชื่อรถยนต์ที่ขึ้นต้นด้วย"M":

cars.stream()
    .filter(car -> Objects.nonNull(car))
    .map(car -> car.getName())
    .filter(carName -> Objects.nonNull(carName))
    .filter(carName -> carName.startsWith("M"))
    .collect(Collectors.toList());

เมื่อคุณคุ้นเคยกับลูกแกะชวเลขแล้วคุณสามารถทำสิ่งนี้ได้:

cars.stream()
    .filter(Objects::nonNull)
    .map(Car::getName)        // Assume the class name for car is Car
    .filter(Objects::nonNull)
    .filter(carName -> carName.startsWith("M"))
    .collect(Collectors.toList());

น่าเสียดายที่เมื่อคุณ.map(Car::getName)คุณจะกลับรายการชื่อไม่ใช่รถยนต์ สวยน้อยกว่า แต่ตอบคำถามอย่างเต็มที่:

cars.stream()
    .filter(car -> Objects.nonNull(car))
    .filter(car -> Objects.nonNull(car.getName()))
    .filter(car -> car.getName().startsWith("M"))
    .collect(Collectors.toList());

1
โปรดทราบว่ารถ null ไม่เป็นปัญหา ในกรณีนี้มันเป็นคุณสมบัติชื่อทำให้เกิดปัญหา ดังนั้นจึงObjects::nonNullไม่สามารถใช้ที่นี่และในคำแนะนำสุดท้ายcars.stream() .filter(car -> Objects.nonNull(car.getName()))ฉันควรจะเชื่อ
kiedysktos

1
BTW ผมคิดว่าcars.stream() .filter(car -> Objects.nonNull(car.getName()) && car.getName().startsWith("M"))จะเป็นบทสรุปของคำแนะนำของคุณในบริบทคำถามนี้
kiedysktos

3
@kiedysktos นั่นเป็นจุดที่ดีที่การโทร.startWithอาจทำให้ตัวชี้ null จุดที่ฉันพยายามทำคือ Java ให้วิธีการเฉพาะสำหรับการกรองวัตถุ null จากสตรีมของคุณ
xbakesx

@ บูธใหญ่ใช่แน่นอนว่าObjects.nonNullเทียบเท่ากับ!= nullตัวเลือกของคุณจะสั้นกว่า
kiedysktos

1
คุณไม่ได้สร้างรายชื่อรถยนต์ ( String) แทนรถยนต์ ( Car) ใช่ไหม
user1803551

59

คุณเพียงแค่ต้องกรองรถยนต์ที่มีnullชื่อ:

requiredCars = cars.stream()
                   .filter(c -> c.getName() != null)
                   .filter(c -> c.getName().startsWith("M"));

3
มันเป็นความอัปยศจริงที่คำตอบนี้ไม่ได้รับการโหวตมากขึ้นเนื่องจากเป็นคำตอบเดียวที่ตอบคำถามได้จริง
Mark Booth

@ MarkBooth คำถาม "จะกรองค่า Null ได้อย่างไร" ดูเหมือนว่าจะตอบได้ดีโดย xbakesx
vegemite4me

@ MarkBooth ดูวันที่คุณถูกต้อง ความผิดพลาดของฉัน.
vegemite4me

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

51

คำตอบที่เสนอนั้นยอดเยี่ยมมาก เพียงแค่ต้องการที่จะแนะนำการปรับปรุงการจัดการกับกรณีของรายการ null โดยใช้Optional.ofNullable, คุณลักษณะใหม่ใน Java 8 :

 List<String> carsFiltered = Optional.ofNullable(cars)
                .orElseGet(Collections::emptyList)
                .stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

ดังนั้นคำตอบทั้งหมดจะเป็น:

 List<String> carsFiltered = Optional.ofNullable(cars)
                .orElseGet(Collections::emptyList)
                .stream()
                .filter(Objects::nonNull) //filtering car object that are null
                .map(Car::getName) //now it's a stream of Strings
                .filter(Objects::nonNull) //filtering null in Strings
                .filter(name -> name.startsWith("M"))
                .collect(Collectors.toList()); //back to List of Strings

5
การใช้งานที่ไม่ถูกต้องของตัวเลือก ไม่ควรใช้ null เป็นคำเหมือนสำหรับคอลเลกชันที่ว่างเปล่าตั้งแต่แรก
VGR

4
@VGR แน่นอน แต่นั่นไม่ใช่สิ่งที่เกิดขึ้นในทางปฏิบัติ บางครั้ง (ส่วนใหญ่) คุณต้องทำงานกับรหัสที่ผู้คนจำนวนมากทำงาน บางครั้งคุณได้รับข้อมูลจากอินเทอร์เฟซภายนอก สำหรับทุกกรณีตัวเลือกเป็นการใช้งานที่ยอดเยี่ยม
Johnny

1
โปรดทราบว่ารถ null ไม่เป็นปัญหา ในกรณีนี้มันเป็นคุณสมบัติชื่อทำให้เกิดปัญหา ดังนั้นObjects::nonNullอย่าแก้ปัญหาเนื่องจากรถยนต์ที่ไม่ใช่ศูนย์สามารถมีชื่อ == null ได้
kiedysktos

แน่นอน @kiedysktos แต่นั่นไม่ใช่สิ่งที่ฉันต้องการแสดงในคำตอบ แต่ฉันยอมรับสิ่งที่คุณพูดและแก้ไขคำตอบ :)
จอห์นนี่

24

คุณสามารถทำได้ในขั้นตอนการกรองเดียว:

requiredCars = cars.stream().filter(c -> c.getName() != null && c.getName().startsWith("M"));

หากคุณไม่ต้องการโทรgetName()หลายครั้ง (เช่นเป็นการโทรราคาแพง) คุณสามารถทำได้:

requiredCars = cars.stream().filter(c -> {
    String name = c.getName();
    return name != null && name.startsWith("M");
});

หรือในวิธีที่ซับซ้อนมากขึ้น:

requiredCars = cars.stream().filter(c -> 
    Optional.ofNullable(c.getName()).filter(name -> name.startsWith("M")).isPresent());

การขยายแบบอินไลน์ในตัวอย่างที่สองมีค่าสำหรับกรณีการใช้ของฉัน
Paul

3

ใช้ประโยชน์จากพลังของjava.util.Optional#map():

List<Car> requiredCars = cars.stream()
  .filter (car -> 
    Optional.ofNullable(car)
      .map(Car::getName)
      .map(name -> name.startsWith("M"))
      .orElse(false) // what to do if either car or getName() yields null? false will filter out the element
    )
  .collect(Collectors.toList())
;

1

คุณสามารถใช้สิ่งนี้

List<Car> requiredCars = cars.stream()
    .filter (t->  t!= null && StringUtils.startsWith(t.getName(),"M"))
    .collect(Collectors.toList());
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.