Nilai filter hanya jika tidak null menggunakan lambda di Java8

160

Saya punya daftar objek mengatakan car. Saya ingin memfilter daftar ini berdasarkan beberapa parameter menggunakan Java 8. Tetapi jika parameternya null, itu melempar NullPointerException. Bagaimana cara menyaring nilai nol?

Kode saat ini adalah sebagai berikut

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

Ini melempar NullPointerExceptionjika getName()kembali null.

vaibhavvc1092
sumber
Apakah Anda ingin "memfilter nilai hanya jika bukan nol" atau "memfilter nilai nol"? Kedengarannya bertentangan dengan saya.
Holger
3
Bisakah saya menyarankan agar Anda menerima jawaban Tunaki karena tampaknya satu-satunya yang benar-benar menjawab pertanyaan Anda.
Mark Booth

Jawaban:

322

Dalam contoh khusus ini saya pikir @ Tagir 100% benar, masukkan ke dalam satu filter dan lakukan dua pemeriksaan. Saya tidak akan menggunakan Optional.ofNullablehal-hal Opsional benar-benar untuk jenis kembali tidak melakukan logika ... tapi benar-benar tidak penting.

Saya ingin menunjukkan bahwa java.util.Objectsada metode yang bagus untuk ini dalam kasus yang luas, sehingga Anda dapat melakukan ini:

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

Yang akan menghapus objek nol Anda. Bagi siapa pun yang tidak terbiasa, itulah kependekan dari yang berikut:

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

Untuk sebagian menjawab pertanyaan yang ada untuk mengembalikan daftar nama mobil yang dimulai dengan "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());

Setelah Anda terbiasa dengan steno lambda Anda juga bisa melakukan ini:

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());

Sayangnya begitu Anda, .map(Car::getName)Anda hanya akan mengembalikan daftar nama, bukan mobil. Jadi kurang cantik tapi sepenuhnya menjawab pertanyaan:

cars.stream()
    .filter(car -> Objects.nonNull(car))
    .filter(car -> Objects.nonNull(car.getName()))
    .filter(car -> car.getName().startsWith("M"))
    .collect(Collectors.toList());
xbakesx
sumber
1
perhatikan bahwa mobil nol bukan masalah. Dalam hal ini, properti namanya menyebabkan masalah. Jadi Objects::nonNulltidak dapat digunakan di sini, dan dalam saran terakhir saya harus cars.stream() .filter(car -> Objects.nonNull(car.getName()))percaya
kiedysktos
1
BTW, saya pikir cars.stream() .filter(car -> Objects.nonNull(car.getName()) && car.getName().startsWith("M"))akan menjadi ringkasan saran Anda dalam konteks pertanyaan ini
kiedysktos
3
@ kiedysktos Itu poin bagus bahwa menelepon .startWithjuga bisa menyebabkan null pointer. Poin yang saya coba buat adalah bahwa Java memasok metode khusus untuk memfilter objek nol dari aliran Anda.
xbakesx
@Mark Booth ya, jelas Objects.nonNullsama dengan != null, opsi Anda lebih pendek
kiedysktos
1
Bukankah Anda membuat daftar nama mobil ( String) alih-alih mobil ( Car)?
user1803551
59

Anda hanya perlu memfilter mobil yang memiliki nullnama:

requiredCars = cars.stream()
                   .filter(c -> c.getName() != null)
                   .filter(c -> c.getName().startsWith("M"));
Tunaki
sumber
3
Sangat memalukan bahwa jawaban ini tidak lebih tinggi karena tampaknya menjadi satu-satunya jawaban yang benar-benar menjawab pertanyaan.
Mark Booth
@MarkBooth Pertanyaan "Bagaimana cara memfilter nilai nol?" tampaknya dijawab dengan baik oleh xbakesx.
vegemite4me
@MarkBooth Melihat tanggal Anda benar. Kesalahanku.
vegemite4me
Dari segi kinerja, lebih baik menyaring aliran dua kali atau lebih baik menggunakan predikat untuk penyaringan? Hanya ingin tahu.
Vaibhav_Sharma
51

Jawaban yang diajukan sangat bagus. Hanya ingin menyarankan perbaikan untuk menangani kasus daftar nol menggunakan Optional.ofNullable, fitur baru di Java 8 :

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

Jadi, jawaban lengkapnya adalah:

 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
Johnny
sumber
5
Penggunaan Opsional salah. null tidak boleh digunakan sebagai sinonim untuk Koleksi kosong di tempat pertama.
VGR
4
@ VGR Tentu saja, tapi bukan itu yang terjadi dalam praktik. Terkadang (sebagian besar waktu) Anda perlu bekerja dengan kode yang banyak orang kerjakan. Terkadang Anda menerima data dari antarmuka eksternal. Untuk semua kasus itu, Opsional sangat bermanfaat.
Johnny
1
perhatikan bahwa mobil nol bukan masalah. Dalam hal ini, properti namanya menyebabkan masalah. Jadi Objects::nonNulltidak menyelesaikan masalah karena mobil non-null dapat memiliki nama == null
kiedysktos
Tentu saja @kiedysktos, tapi bukan itu yang ingin saya tunjukkan dalam jawabannya. Tapi, saya menerima apa yang Anda katakan dan mengedit jawabannya :)
Johnny
24

Anda dapat melakukan ini dalam langkah filter tunggal:

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

Jika Anda tidak ingin menelepon getName()beberapa kali (misalnya, ini panggilan mahal), Anda dapat melakukan ini:

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

Atau dengan cara yang lebih canggih:

requiredCars = cars.stream().filter(c -> 
    Optional.ofNullable(c.getName()).filter(name -> name.startsWith("M")).isPresent());
Tagir Valeev
sumber
Ekspansi sebaris pada contoh kedua sangat berharga untuk kasus penggunaan saya
Paul
3

Memanfaatkan kekuatan 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())
;
rslemos
sumber
1

Anda bisa menggunakan ini

List<Car> requiredCars = cars.stream()
    .filter (t->  t!= null && StringUtils.startsWith(t.getName(),"M"))
    .collect(Collectors.toList());
riverfan
sumber