Haruskah Java 8 getter mengembalikan tipe opsional?

288

Optional tipe yang diperkenalkan di Java 8 adalah hal baru bagi banyak pengembang.

Apakah metode rajinik mengembalikan Optional<Foo>tipe klasik Foobukan praktik yang baik? Asumsikan nilainya bisa null.

leonprou
sumber
8
Meskipun ini cenderung menarik jawaban yang memiliki pendapat, itu adalah pertanyaan yang bagus. Saya menantikan jawaban dengan fakta nyata tentang masalah ini.
Justin
8
Pertanyaannya adalah apakah nullablitity tidak dapat dihindari. Sebuah komponen mungkin memiliki properti yang diizinkan menjadi nol, tetapi tetap saja, pemrogram yang menggunakan komponen itu mungkin memutuskan untuk menjaga properti itu secara ketat null. Jadi programmer tidak harus berurusan dengan Optionalitu. Atau, dengan kata lain, nullbenar - benar mewakili tidak adanya nilai seperti dengan hasil pencarian (jika Optionalperlu) atau nullhanya satu anggota dari himpunan nilai yang mungkin.
Holger
1
Lihat juga diskusi tentang @NotNullanotasi: stackoverflow.com/q/4963300/873282
koppor

Jawaban:

515

Tentu saja, orang akan melakukan apa yang mereka inginkan. Tetapi kami memang memiliki niat yang jelas ketika menambahkan fitur ini, dan itu bukan untuk tujuan umum. Mungkin mengetik, sebanyak yang orang ingin kami melakukannya. Tujuan kami adalah untuk menyediakan mekanisme terbatas untuk tipe pengembalian metode perpustakaan di mana perlu ada cara yang jelas untuk mewakili "tidak ada hasil", dan menggunakan nulluntuk itu sangat mungkin menyebabkan kesalahan.

Misalnya, Anda mungkin tidak boleh menggunakannya untuk sesuatu yang mengembalikan array hasil, atau daftar hasil; alih-alih mengembalikan array atau daftar kosong. Anda hampir tidak boleh menggunakannya sebagai bidang sesuatu atau parameter metode.

Saya pikir secara rutin menggunakannya sebagai nilai balik untuk getter pasti akan digunakan berlebihan.

Tidak ada yang salah dengan Opsional yang harus dihindari, hanya saja tidak seperti yang diinginkan banyak orang, dan karenanya kami cukup khawatir tentang risiko penggunaan berlebihan yang bersemangat.

(Pengumuman layanan publik: JANGAN PERNAH menelepon Optional.getkecuali Anda dapat membuktikannya tidak akan pernah batal; alih-alih gunakan salah satu metode yang aman seperti orElseatau ifPresent. Dalam retrospeksi, kita seharusnya memanggil getsesuatu seperti getOrElseThrowNoSuchElementExceptionatau sesuatu yang membuatnya lebih jelas bahwa ini adalah metode yang sangat berbahaya. yang menggerogoti seluruh tujuan Optionalsejak awal. Hal yang dipelajari (UPDATE: Java 10 memiliki Optional.orElseThrow(), yang secara semantik setara dengan get(), tetapi yang namanya lebih sesuai.))

Brian Goetz
sumber
24
(Mengenai bagian terakhir) ... dan ketika kita yakin, bahwa nilainya tidak pernah nullkita gunakan orElseThrow(AssertionError::new), ahem atau orElseThrow(NullPointerException::new)...
Holger
25
Apa yang akan Anda lakukan secara berbeda jika niat Anda telah berkunjung ke memperkenalkan tujuan umum Mungkin atau Beberapa jenis? Apakah ada cara di mana Opsional tidak sesuai dengan tagihan untuk satu, atau hanya bahwa memperkenalkan Opsional di seluruh API baru akan membuatnya non-Jawa-ish?
David Moles
22
Dengan "tipe tujuan umum", saya maksud membangunnya ke dalam sistem tipe bahasa, daripada menyediakan kelas perpustakaan yang mendekati itu. (Beberapa bahasa memiliki tipe untuk T? (T atau null) dan T! (Non-nullable T)) Opsional hanyalah kelas; kami tidak dapat melakukan konversi tersirat antara Foo dan Opsional <Foo> seperti yang kami dapat dengan dukungan bahasa.
Brian Goetz
11
Saya sempat bertanya-tanya mengapa penekanan di dunia Jawa adalah pada Opsional, dan bukan analisis statis yang lebih baik. Opsional memang memiliki beberapa keunggulan, tetapi keunggulan besar yang nulldimiliki adalah kompatibilitas mundur; Map::getmengembalikan nullable V, bukan an Optional<V>, dan itu tidak akan pernah berubah. Itu bisa dengan mudah dijelaskan @Nullable. Sekarang kami memiliki dua cara untuk mengekspresikan kurangnya nilai, ditambah insentif yang lebih sedikit untuk benar-benar mewujudkan analisis statis, yang sepertinya merupakan posisi yang lebih buruk.
yshavit
21
Saya tidak tahu bahwa menggunakannya sebagai nilai pengembalian properti bukanlah apa yang Anda maksudkan sampai saya membaca jawaban ini di sini di StackOverflow. Bahkan, saya merasa yakin bahwa itulah yang Anda maksudkan setelah membaca artikel ini di situs web Oracle: oracle.com/technetwork/articles/java/... Terima kasih telah mengklarifikasi
Jason Thompson
73

Setelah melakukan sedikit riset sendiri, saya telah menemukan sejumlah hal yang mungkin menyarankan saat ini tepat. Yang paling otoritatif adalah kutipan berikut dari artikel Oracle:

"Penting untuk dicatat bahwa maksud dari kelas Opsional bukan untuk menggantikan setiap referensi nol tunggal . Sebagai gantinya, tujuannya adalah untuk membantu merancang API yang lebih dapat dipahami sehingga dengan hanya membaca tanda tangan suatu metode, Anda dapat mengetahui apakah Anda dapat mengharapkan nilai opsional. Ini memaksa Anda untuk secara aktif membuka opsi untuk berurusan dengan tidak adanya nilai. " - Bosan dengan Pengecualian Null Pointer? Pertimbangkan Menggunakan Java SE 8 Opsional!

Saya juga menemukan kutipan ini dari Java 8 Opsional: Cara menggunakannya

"Opsional tidak dimaksudkan untuk digunakan dalam konteks ini, karena tidak akan memberi kita apa pun:

  • di lapisan model domain (tidak bisa serial)
  • dalam DTO (alasan yang sama)
  • dalam parameter input metode
  • dalam parameter konstruktor "

Yang sepertinya juga menaikkan beberapa poin yang valid.

Saya tidak dapat menemukan konotasi negatif atau bendera merah untuk menunjukkan bahwa Optionalharus dihindari. Saya pikir ide umumnya adalah, jika itu membantu atau meningkatkan kegunaan API Anda, gunakanlah.

Justin
sumber
11
stackoverflow.com/questions/25693309 - tampaknya Jackson sudah mendukungnya, jadi "tidak serializable" tidak lagi berlaku sebagai alasan yang sah :)
Vlasec
1
Saya sarankan memiliki alamat jawaban Anda mengapa menggunakan Optionalparameter input metode (lebih khusus konstruktor) "tidak akan membelikan kami apa pun". dolszewski.com/java/java-8-optional-use-cases berisi penjelasan yang bagus.
Gili
Saya telah menemukan bahwa hasil opsional currying yang perlu diubah menjadi API lain memerlukan parameter opsional. Hasilnya adalah API yang cukup dapat dipahami. Lihat stackoverflow.com/a/31923105/105870
Karl the Pagan
1
Tautan rusak. Bisakah Anda memperbarui jawabannya?
softarn
2
Masalahnya adalah, seringkali tidak meningkatkan API, meskipun niat terbaik dari pengembang. Saya telah mengumpulkan contoh-contoh penggunaan Optional yang buruk , semua diambil dari kode produksi, yang dapat Anda periksa.
MiguelMunoz
20

Saya akan mengatakan secara umum itu ide yang baik untuk menggunakan tipe opsional untuk nilai pengembalian yang dapat dibatalkan. Namun, wrt to frameworks saya berasumsi bahwa mengganti getter klasik dengan tipe opsional akan menyebabkan banyak masalah ketika bekerja dengan frameworks (misalnya, Hibernate) yang mengandalkan konvensi pengkodean untuk getter dan setter.

Claas Wilke
sumber
14
Saran ini persis seperti yang saya maksudkan dengan "kami khawatir tentang risiko penggunaan berlebihan yang berlebihan" di stackoverflow.com/a/26328555/3553087 .
Brian Goetz
13

Alasan Optionalditambahkan ke Jawa adalah karena ini:

return Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .findFirst()
    .getOrThrow(() -> new InternalError(...));

lebih bersih dari ini:

Method matching =
    Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .getFirst();
if (matching == null)
  throw new InternalError("Enclosing method not found");
return matching;

Maksud saya adalah bahwa Opsional ditulis untuk mendukung pemrograman fungsional , yang ditambahkan ke Jawa pada saat yang sama. (Contoh berasal dari blog oleh Brian Goetz . Contoh yang lebih baik mungkin menggunakan orElse()metode ini, karena kode ini akan memunculkan pengecualian, tetapi Anda mendapatkan gambarnya.)

Tetapi sekarang, orang menggunakan Opsional untuk alasan yang sangat berbeda. Mereka menggunakannya untuk mengatasi kekurangan dalam desain bahasa. Kekurangannya adalah ini: Tidak ada cara untuk menentukan parameter API mana dan nilai pengembalian yang boleh nol. Mungkin disebutkan di javadocs, tetapi kebanyakan pengembang bahkan tidak menulis javadocs untuk kode mereka, dan tidak banyak yang akan memeriksa javadocs ketika mereka menulis. Jadi ini mengarah ke banyak kode yang selalu memeriksa nilai-nilai nol sebelum menggunakannya, meskipun mereka sering tidak mungkin menjadi nol karena mereka sudah divalidasi berulang kali sembilan atau sepuluh kali tumpukan panggilan.

Saya pikir ada kehausan nyata untuk menyelesaikan kekurangan ini, karena begitu banyak orang yang melihat kelas Opsional baru berasumsi tujuannya adalah untuk menambah kejelasan pada API. Itulah sebabnya orang-orang mengajukan pertanyaan seperti "apakah getter mengembalikan Opsional?" Tidak, mereka mungkin tidak boleh, kecuali jika Anda mengharapkan getter untuk digunakan dalam pemrograman fungsional, yang sangat tidak mungkin. Bahkan, jika Anda melihat di mana Opsional digunakan di Java API, itu terutama di kelas Stream, yang merupakan inti dari pemrograman fungsional. (Saya belum memeriksa dengan teliti, tetapi kelas Stream mungkin satu - satunya tempat yang mereka gunakan.)

Jika Anda berencana untuk menggunakan pengambil dalam sedikit kode fungsional, mungkin ide yang baik untuk memiliki pengambil standar dan yang kedua yang mengembalikan opsional.

Oh, dan jika Anda perlu kelas Anda menjadi serializable, Anda harus benar-benar tidak menggunakan Opsional.

Opsional adalah solusi yang sangat buruk untuk cacat API karena a) mereka sangat bertele-tele, dan b) Mereka tidak pernah dimaksudkan untuk menyelesaikan masalah itu sejak awal.

Solusi yang jauh lebih baik untuk cacat API adalah Pemeriksa Nullness . Ini adalah prosesor anotasi yang memungkinkan Anda menentukan parameter dan nilai pengembalian yang diizinkan menjadi nol dengan membubuhi keterangan dengan @Nullable. Dengan cara ini, kompiler dapat memindai kode dan mencari tahu apakah nilai yang benar-benar dapat null dilewatkan ke nilai di mana nol tidak diizinkan. Secara default, ini mengasumsikan tidak ada yang diizinkan menjadi nol kecuali jika dijelaskan demikian. Dengan cara ini, Anda tidak perlu khawatir tentang nilai nol. Melewati nilai nol ke parameter akan menghasilkan kesalahan kompiler. Menguji objek untuk null yang tidak bisa nol menghasilkan peringatan kompiler. Efeknya adalah mengubah NullPointerException dari kesalahan runtime menjadi kesalahan waktu kompilasi.

Ini mengubah segalanya.

Sedangkan untuk getter Anda, jangan gunakan Opsional. Dan cobalah untuk merancang kelas Anda sehingga tidak ada anggota yang bisa menjadi nol. Dan mungkin mencoba menambahkan Nullness Checker ke proyek Anda dan mendeklarasikan parameter getter dan setter Anda @Nullable jika mereka membutuhkannya. Saya hanya melakukan ini dengan proyek-proyek baru. Mungkin menghasilkan banyak peringatan dalam proyek yang ada ditulis dengan banyak tes berlebihan untuk null, jadi mungkin sulit untuk memperbaiki. Tetapi juga akan menangkap banyak bug. Aku menyukainya. Kode saya jauh lebih bersih dan lebih andal karenanya.

(Ada juga bahasa baru yang membahas hal ini. Kotlin, yang mengkompilasi ke kode byte Java, memungkinkan Anda untuk menentukan apakah suatu objek mungkin nol ketika Anda mendeklarasikannya. Ini adalah pendekatan yang lebih bersih.)

Tambahan untuk Posting Asli (versi 2)

Setelah memberikannya banyak pemikiran, saya dengan enggan sampai pada kesimpulan bahwa dapat diterima untuk kembali Opsional dengan satu syarat: Bahwa nilai yang diambil mungkin sebenarnya nol. Saya telah melihat banyak kode di mana orang-orang secara rutin mengembalikan Opsional dari getter yang tidak mungkin mengembalikan null. Saya melihat ini sebagai praktik pengkodean yang sangat buruk yang hanya menambah kompleksitas kode, yang membuat bug lebih mungkin. Tetapi ketika nilai yang dikembalikan mungkin benar-benar nol, lanjutkan dan bungkus dalam Opsional.

Perlu diingat bahwa metode yang dirancang untuk pemrograman fungsional, dan yang memerlukan referensi fungsi, akan (dan harus) ditulis dalam dua bentuk, salah satunya menggunakan Opsional. Sebagai contoh, Optional.map()dan Optional.flatMap()keduanya mengambil referensi fungsi. Yang pertama mengambil referensi ke pengambil biasa, dan yang kedua mengambil yang mengembalikan Opsional. Jadi, Anda tidak melakukan bantuan siapa pun dengan mengembalikan Opsional di mana nilainya tidak boleh nol.

Setelah mengatakan semua itu, saya masih melihat pendekatan yang digunakan oleh Nullness Checker adalah cara terbaik untuk menangani nulls, karena mereka mengubah NullPointerExceptions dari bug runtime untuk mengkompilasi kesalahan waktu.

MiguelMunoz
sumber
2
Jawaban ini sepertinya paling cocok untuk saya. Opsional hanya ditambahkan di java 8 ketika stream ditambahkan. Dan hanya fungsi streaming mengembalikan opsional sejauh yang saya lihat.
Archit
1
Saya pikir ini adalah salah satu jawaban terbaik. Orang tahu apa yang Opsional dan bagaimana cara kerjanya. Tetapi bagian yang paling membingungkan adalah / di mana menggunakannya dan bagaimana menggunakannya. dengan membaca ini, itu menghilangkan banyak keraguan.
ParagFlume
3

Jika Anda menggunakan serializers modern dan kerangka kerja lain yang mengerti Optionalmaka saya telah menemukan pedoman ini berfungsi dengan baik ketika menulis Entitykacang dan lapisan domain:

  1. Jika lapisan serialisasi (biasanya DB) memungkinkan nullnilai untuk sel dalam kolom BARdalam tabel FOO, maka pengambil Foo.getBar()dapat kembali Optionalmenunjukkan kepada pengembang bahwa nilai ini mungkin diharapkan menjadi nol dan mereka harus menangani ini. Jika DB menjamin nilainya tidak akan nol maka pengambil harus tidak membungkusnya dengan Optional.
  2. Foo.barharus privatedan tidak harus Optional. Tidak ada alasan untuk itu Optionaljika memang benar private.
  3. Setter Foo.setBar(String bar)harus mengambil tipe bardan bukan Optional . Jika OK untuk menggunakan nullargumen maka sebutkan ini dalam komentar JavaDoc. Jika tidak OK untuk menggunakan nullsebuah IllegalArgumentExceptionatau beberapa logika bisnis yang tepat adalah, IMHO, lebih tepat.
  4. Konstruktor tidak memerlukan Optionalargumen (untuk alasan yang mirip dengan poin 3). Secara umum saya hanya menyertakan argumen dalam konstruktor yang harus non-null dalam database serialisasi.

Untuk membuat hal di atas lebih efisien, Anda mungkin ingin mengedit templat IDE Anda untuk menghasilkan getter dan templat yang sesuai untuk toString(), equals(Obj o)dll. Atau menggunakan bidang langsung untuk itu (kebanyakan generator IDE sudah berurusan dengan nol).

Menandai
sumber