Mengapa lemparan ini java.lang.NullPointerException
?
List<String> strings = new ArrayList<>();
strings.add(null);
strings.add("test");
String firstString = strings.stream()
.findFirst() // Exception thrown here
.orElse("StringWhenListIsEmpty");
//.orElse(null); // Changing the `orElse()` to avoid ambiguity
Item pertama masuk strings
adalah null
, yang merupakan nilai yang dapat diterima dengan sempurna. Selanjutnya, findFirst()
mengembalikan sebuah Opsional , yang lebih masuk akal untuk findFirst()
dapat menangani null
s.
EDIT: memperbarui orElse()
menjadi kurang ambigu.
java
java-8
java-stream
optional
tidak pernah berakhirqs
sumber
sumber
String
sini, bagaimana jika itu adalah daftar yang mewakili kolom di DB? Nilai baris pertama untuk kolom itu bisanull
.null
adalah nilai yang dapat diterima secara sempurna di Java, secara umum. Secara khusus, ini adalah elemen yang valid untuk fileArrayList<String>
. Namun, seperti nilai lainnya, ada batasan tentang apa yang dapat dilakukan dengannya. "Jangan pernah menggunakannull
" bukanlah nasihat yang berguna, karena Anda tidak dapat menghindarinya.findFirst()
, tidak ada lagi yang ingin Anda lakukan.Jawaban:
Alasan untuk ini adalah penggunaan
Optional<T>
dalam pengembalian. Opsional tidak boleh mengandungnull
. Pada dasarnya, ia tidak menawarkan cara untuk membedakan situasi "itu tidak ada" dan "itu ada di sana, tetapi sudah diatur kenull
".Itulah mengapa dokumentasi secara eksplisit melarang situasi saat
null
dipilih difindFirst()
:sumber
Optional
. Bagaimanapun, saya pikir saya hampir mengomel - jika bahasanya tidak mendukung, itu tidak mendukungnya.boolean
untuk membedakan kedua situasi ini akan sangat masuk akal. Bagi saya, sepertinya penggunaan diOptional<T>
sini adalah pilihan yang dipertanyakan.Iterable
jenis, memeriksahasNext()
, dan mengembalikan nilai yang sesuai.findFirst
mengembalikan nilai Opsional kosong dalam kasus POSeperti yang telah dibahas , perancang API tidak berasumsi bahwa pengembang ingin memperlakukan
null
nilai dan nilai yang tidak ada dengan cara yang sama.Jika Anda masih ingin melakukannya, Anda dapat melakukannya secara eksplisit dengan menerapkan urutan
ke sungai. Hasilnya akan menjadi opsional kosong dalam kedua kasus, jika tidak ada elemen pertama atau jika elemen pertama adalah
null
. Jadi dalam kasus Anda, Anda dapat menggunakanString firstString = strings.stream() .map(Optional::ofNullable).findFirst().flatMap(Function.identity()) .orElse(null);
untuk mendapatkan
null
nilai jika elemen pertama tidak ada ataunull
.Jika Anda ingin membedakan kasus-kasus ini, Anda dapat mengabaikan
flatMap
langkah berikut:Optional<String> firstString = strings.stream() .map(Optional::ofNullable).findFirst().orElse(null); System.out.println(firstString==null? "no such element": firstString.orElse("first element is null"));
Ini tidak jauh berbeda dengan pertanyaan Anda yang diperbarui. Anda hanya perlu mengganti
"no such element"
dengan"StringWhenListIsEmpty"
dan"first element is null"
dengannull
. Tetapi jika Anda tidak menyukai persyaratan, Anda dapat mencapainya juga seperti:String firstString = strings.stream().skip(0) .map(Optional::ofNullable).findFirst() .orElseGet(()->Optional.of("StringWhenListIsEmpty")) .orElse(null);
Sekarang,
firstString
akan menjadinull
jika elemen ada tetapi adanull
dan akan terjadi"StringWhenListIsEmpty"
ketika tidak ada elemen.sumber
null
untuk 1) elemen pertamanull
atau 2) tidak ada elemen di dalam daftar. Saya telah memperbarui pertanyaan untuk menghilangkan ambiguitas.Optional
mungkin ditugaskan kenull
. KarenaOptional
seharusnya menjadi "tipe nilai", itu tidak boleh nol. Dan Opsional tidak boleh dibandingkan dengan==
. Kode mungkin gagal di Java 10 :) atau tipe nilai kapan saja diperkenalkan ke Java.null
dan meskipun instance tidak boleh dibandingkan==
, referensi tersebut dapat diuji untuknull
digunakan==
karena itulah satu - satunya cara untuk mengujinyanull
. Saya tidak dapat melihat bagaimana transisi ke "tidak pernahnull
" seharusnya berfungsi untuk kode yang ada karena nilai default untuk semua variabel instance dan elemen array adalahnull
. Cuplikannya pasti bukan kode terbaik tetapi juga bukan tugas memperlakukannull
s sebagai nilai sekarang.null
. Namun, karena perubahan bahasa hipotetis seperti itu akan menyebabkan kompiler mengeluarkan kesalahan di sini (tidak memecahkan kode secara diam-diam), saya dapat menerima fakta, bahwa itu mungkin harus diadaptasi untuk Java 10. Saya kira,Stream
API akan terlihat sangat berbeda saat itu juga ...Anda dapat menggunakan
java.util.Objects.nonNull
untuk memfilter daftar sebelum menemukansesuatu seperti
sumber
firstString
menjadinull
jika item pertamastrings
adalahnull
.Optional.of
yang tidak aman nol. Anda bisamap
keOptional.ofNullable
dan kemudian menggunakanfindFirst
tetapi Anda akan berakhir dengan pilihan dari OpsionalKode berikut menggantikan
findFirst()
denganlimit(1)
dan menggantikanorElse()
denganreduce()
:String firstString = strings. stream(). limit(1). reduce("StringWhenListIsEmpty", (first, second) -> second);
limit()
memungkinkan hanya 1 elemen untuk dijangkaureduce
. TheBinaryOperator
passing toreduce
mengembalikan 1 elemen itu atau"StringWhenListIsEmpty"
jika tidak ada elemen yang mencapaireduce
.Keunggulan dari solusi ini
Optional
adalah tidak dialokasikan danBinaryOperator
lambda tidak akan mengalokasikan apa pun.sumber
Opsional seharusnya menjadi tipe "nilai". (baca cetakan kecilnya di javadoc :) JVM bahkan dapat menggantikan semua
Optional<Foo>
hanya denganFoo
, menghapus semua biaya tinju dan unboxing. Anull
Foo artinya kosongOptional<Foo>
.Ini adalah desain yang memungkinkan untuk mengizinkan Opsional dengan nilai null, tanpa menambahkan tanda boolean - cukup tambahkan objek sentinel. (bahkan bisa digunakan
this
sebagai sentinel; lihat Throwable.cause)Keputusan bahwa Opsional tidak dapat membungkus null tidak didasarkan pada biaya runtime. Ini adalah masalah yang sangat diperdebatkan dan Anda perlu menggali milis. Keputusan tidak meyakinkan semua orang.
Dalam kasus apa pun, karena Opsional tidak dapat membungkus nilai null, itu mendorong kita ke sudut dalam kasus seperti
findFirst
. Mereka pasti beralasan bahwa nilai null sangat jarang (bahkan dianggap bahwa Stream harus melarang nilai null), oleh karena itu akan lebih mudah untuk melemparkan pengecualian pada nilai null daripada di streaming kosong.Solusinya adalah dengan kotak
null
, misalnyaclass Box<T> static Box<T> of(T value){ .. } Optional<Box<String>> first = stream.map(Box::of).findFirst();
(Mereka mengatakan solusi untuk setiap masalah OOP adalah dengan memperkenalkan tipe lain :)
sumber
Box
tipe lain . TheOptional
jenis itu sendiri dapat melayani tujuan ini. Lihat jawaban saya sebagai contoh.null
adalah nilai yang valid seperti yang lain, tidak ada perlakuan khusus untuk itu. (sampai beberapa saat kemudian :)