Mengapa menggunakan Opsional di Java 8+ alih-alih pemeriksaan pointer nol tradisional?

110

Kami baru saja pindah ke Java 8. Sekarang, saya melihat aplikasi dibanjiri Optionalobjek.

Sebelum Java 8 (Gaya 1)

Employee employee = employeeServive.getEmployee();

if(employee!=null){
    System.out.println(employee.getId());
}

After Java 8 (Gaya 2)

Optional<Employee> employeeOptional = Optional.ofNullable(employeeService.getEmployee());
if(employeeOptional.isPresent()){
    Employee employee = employeeOptional.get();
    System.out.println(employee.getId());
}

Saya tidak melihat nilai tambah Optional<Employee> employeeOptional = employeeService.getEmployee();ketika layanan itu sendiri kembali opsional:

Berasal dari latar belakang Java 6, saya melihat Style 1 lebih jelas dan dengan lebih sedikit baris kode. Apakah ada keuntungan nyata yang saya lewatkan di sini?

Dikonsolidasikan dari pemahaman dari semua jawaban dan penelitian lebih lanjut di blog

pengguna3198603
sumber
20
Oracle memiliki artikel yang luas tentang penggunaan Opsional .
Mike Partridge
28
Huek! employeeOptional.isPresent()tampaknya sepenuhnya kehilangan titik jenis opsi. Sesuai komentar @ MikePartridge, ini tidak direkomendasikan atau idiomatik. Secara eksplisit memeriksa apakah jenis opsi adalah sesuatu atau tidak sama sekali adalah tidak-tidak. Anda seharusnya hanya mapatau flatMaplebih dari mereka.
Andres F.
7
Lihat sebuah Stack Overflow Jawaban atasOptional oleh Brian Goetz , yang Arsitek Bahasa Jawa di Oracle.
Basil Bourque
6
Inilah yang terjadi ketika Anda mematikan otak Anda atas nama "praktik terbaik".
immibis
9
Itu adalah penggunaan opsional yang cukup buruk tetapi bahkan yang memiliki manfaat. Dengan nol, semuanya mungkin nol, jadi Anda harus terus-menerus memeriksa, opsi opsional menyatakan bahwa variabel tertentu ini kemungkinan akan hilang, pastikan Anda memeriksanya
Richard Tingle

Jawaban:

108

Style 2 tidak akan cukup Java 8 untuk melihat manfaat penuh. Anda tidak menginginkannya if ... usesama sekali. Lihat contoh Oracle . Mengambil saran mereka, kami mendapatkan:

Gaya 3

// Changed EmployeeServive to return an optional, no more nulls!
Optional<Employee> employee = employeeServive.getEmployee();
employee.ifPresent(e -> System.out.println(e.getId()));

Atau cuplikan yang lebih panjang

Optional<Employee> employee = employeeServive.getEmployee();
// Sometimes an Employee has forgotten to write an up-to-date timesheet
Optional<Timesheet> timesheet = employee.flatMap(Employee::askForCurrentTimesheet); 
// We don't want to do the heavyweight action of creating a new estimate if it will just be discarded
client.bill(timesheet.orElseGet(EstimatedTimesheet::new));
Caleth
sumber
19
memang kami melarang sebagian besar penggunaan isPresent (ditangkap oleh ulasan kode)
jk.
10
@jk. memang, jika ada kelebihan void ifPresent(Consumer<T>, Runnable)saya akan berdebat untuk larangan total isPresentdanget
Caleth
10
@Caleth: Anda mungkin tertarik pada Java 9's ifPresentOrElse(Consumer<? super T>, Runnable).
wchargin
9
Saya menemukan satu kelemahan dalam gaya 8 java ini, dibandingkan dengan java 6 satu: itu bodoh alat cakupan kode. Dengan pernyataan if, ini menunjukkan saat Anda melewatkan cabang, bukan di sini.
Thierry
14
@ Harun "secara drastis mengurangi pembacaan kode". Bagian mana yang tepatnya mengurangi keterbacaan? Kedengarannya lebih seperti "Saya selalu melakukannya secara berbeda dan saya tidak ingin mengubah kebiasaan saya" daripada alasan obyektif.
Voo
47

Jika Anda menggunakan Optionalsebagai "kompatibilitas" lapisan antara API yang lebih tua yang mungkin masih kembali null, mungkin akan membantu untuk membuat (non-kosong) Opsional pada tahap terbaru yang Anda yakin memiliki sesuatu. Misalnya, di mana Anda menulis:

Optional<Employee> employeeOptional = Optional.ofNullable(employeeService.getEmployee());
if(employeeOptional.isPresent()){
    Employee employeeOptional= employeeOptional.get();
    System.out.println(employee.getId());
}

Saya akan memilih:

Optional.of(employeeService)                 // definitely have the service
        .map(EmployeeService::getEmployee)   // getEmployee() might return null
        .map(Employee::getId)                // get ID from employee if there is one
        .ifPresent(System.out::println);     // and if there is an ID, print it

Intinya adalah Anda tahu bahwa ada layanan karyawan yang tidak nol , sehingga Anda dapat membungkusnya Optionaldengan Optional.of(). Kemudian, ketika Anda memanggil getEmployee()itu, Anda mungkin atau mungkin tidak mendapatkan karyawan. Karyawan itu mungkin (atau, mungkin, mungkin tidak) memiliki ID. Kemudian, jika Anda berakhir dengan ID, Anda ingin mencetaknya.

Tidak perlu secara eksplisit memeriksa adanya null, kehadiran, dll., Dalam kode ini.

Joshua Taylor
sumber
4
Apa keuntungan menggunakan (...).ifPresent(aMethod)lebih if((...).isPresent()) { aMethod(...); }? Tampaknya hanya menjadi sintaks lain untuk melakukan operasi yang hampir sama.
Ruslan
2
@Ruslan Mengapa menggunakan satu sintaks khusus untuk satu panggilan tertentu ketika Anda sudah memiliki solusi umum yang berfungsi sama baiknya untuk semua kasus? Kami hanya menerapkan ide yang sama dari peta dan juga di sini.
Voo
1
@Ruslan ... API mengembalikan nulls bukan koleksi atau array kosong. Misalnya, Anda dapat melakukan sesuatu seperti (dengan asumsi kelas Induk yang getChildren () mengembalikan satu set anak, atau nol jika tidak ada anak): Set<Children> children = Optional.of(parent).map(Parent::getChildren).orElseGet(Collections::emptySet); Sekarang saya dapat memproses childrensecara seragam, misalnya children.forEach(relative::sendGift);,. Mereka bahkan bisa dikombinasikan: Optional.of(parent).map(Parent::getChildren).orElseGet(Collections::emptySet).forEach(relative::sendGift);. Semua pelat kosong dari pemeriksaan nol, dll., ...
Joshua Taylor
1
@Ruslan ... didelegasikan ke kode mencoba dan benar di pustaka standar. Itu mengurangi jumlah kode yang saya, sebagai programmer, harus tulis, uji, debug, dll.
Joshua Taylor
1
Senang saya menggunakan Swift. jika biarkan employee = employeeService.employee {print (employee.Id); }
gnasher729
23

Di samping tidak ada nilai tambah dalam memiliki Optionalnilai tunggal. Seperti yang Anda lihat, itu hanya mengganti cek nulldengan cek keberadaan.

Ada besar nilai tambah dalam memiliki Collectionhal-hal seperti itu. Semua metode streaming yang diperkenalkan untuk menggantikan loop tingkat rendah gaya lama memahami Optionalhal-hal dan melakukan hal yang benar tentang mereka (tidak memprosesnya atau akhirnya mengembalikan yang tidak disetel Optional). Jika Anda berurusan dengan n item lebih sering daripada dengan item individual (dan 99% dari semua program realistis melakukannya), maka itu adalah peningkatan besar untuk memiliki dukungan bawaan untuk hal-hal yang ada secara opsional. Dalam kasus ideal Anda tidak perlu memeriksa null atau hadir.

Kilian Foth
sumber
24
Selain koleksi / sungai, Opsional ini juga bagus untuk membuat API lebih mendokumentasikan diri, misalnya Employee getEmployee()vs Optional<Employee> getEmployee(). Meskipun secara teknis keduanya bisa kembali null, salah satunya jauh lebih mungkin menyebabkan masalah.
amon
16
Ada banyak nilai dalam opsional satu nilai. Mampu .map()tanpa harus memeriksa nol sepanjang jalan itu bagus. Misalnya Optional.of(service).map(Service::getEmployee).map(Employee::getId).ifPresent(this::submitIdForReview);,.
Joshua Taylor
9
Saya tidak setuju dengan komentar awal Anda tanpa nilai tambah. Opsional memberikan konteks ke kode Anda. Pandangan sekilas dapat memberi tahu Anda mengoreksi kebenaran suatu fungsi dan semua kasing tertutup. Sedangkan dengan cek nol, haruskah Anda null memeriksa setiap jenis referensi tunggal pada setiap metode tunggal? Kasus serupa dapat diterapkan pada Kelas dan modifikator publik / swasta; mereka menambahkan nilai nol ke kode Anda, kan?
ArTs
3
@JoshuaTaylor Jujur, dibandingkan dengan yang ekspresi, saya lebih suka kuno cek null.
Kilian Foth
2
Keuntungan besar lain dari Opsional meskipun dengan nilai tunggal adalah Anda tidak bisa lupa untuk memeriksa nol ketika Anda harus melakukannya. Kalau tidak, sangat mudah untuk melupakan cek dan berakhir dengan NullPointerException ...
Sean Burton
17

Selama Anda menggunakan Optionalseperti API mewah untuk isNotNull(), maka ya, Anda tidak akan menemukan perbedaan dengan hanya memeriksa null.

Hal-hal yang TIDAK boleh Anda gunakan Optionaluntuk

Menggunakan Optionalhanya untuk memeriksa keberadaan suatu nilai adalah Bad Code ™:

// BAD CODE ™ --  just check getEmployee() != null  
Optional<Employee> employeeOptional =  Optional.ofNullable(employeeService.getEmployee());  
if(employeeOptional.isPresent()) {  
    Employee employee = employeeOptional.get();  
    System.out.println(employee.getId());
}  

Hal-hal yang harus Anda gunakan Optionaluntuk

Hindari nilai yang tidak ada, dengan memproduksinya jika perlu saat menggunakan metode API pengembalian-nol:

Employee employee = Optional.ofNullable(employeeService.getEmployee())
                            .orElseGet(Employee::new);
System.out.println(employee.getId());

Atau, jika membuat Karyawan baru terlalu mahal:

Optional<Employee> employee = Optional.ofNullable(employeeService.getEmployee());
System.out.println(employee.map(Employee::getId).orElse("No employee found"));

Juga, buat semua orang tahu bahwa metode Anda mungkin tidak mengembalikan nilai (jika, karena alasan apa pun, Anda tidak dapat mengembalikan nilai default seperti di atas):

// Your code without Optional
public Employee getEmployee() {
    return someCondition ? null : someEmployee;
}
// Someone else's code
Employee employee = getEmployee(); // compiler doesn't complain
// employee.get...() -> NPE awaiting to happen, devs criticizing your code

// Your code with Optional
public Optional<Employee> getEmployee() {
    return someCondition ? Optional.empty() : Optional.of(someEmployee);
}
// Someone else's code
Employee employee = getEmployee(); // compiler complains about incompatible types
// Now devs either declare Optional<Employee>, or just do employee = getEmployee().get(), but do so consciously -- not your fault.

Dan akhirnya, ada semua metode seperti aliran yang telah dijelaskan dalam jawaban lain, meskipun itu tidak benar-benar Anda memutuskan untuk menggunakan Optionaltetapi memanfaatkan yang Optionaldisediakan oleh orang lain.

walen
sumber
1
@ Kapap Memang. Kami berdebat di / r / java, dan saya berkata : «Saya melihat Optionalsebagai peluang yang terlewatkan. "Ini, Optionalbuat benda baru ini yang kubuat sehingga kamu terpaksa memeriksa nilai nol. Oh, dan omong-omong ... aku menambahkan get()metode untuk itu sehingga kamu tidak benar-benar harus memeriksa apa pun;);)" . (...) Optionalbagus sebagai tanda neon "INI MUNGKIN MENJADI NULL", tetapi keberadaannya yang telanjang get()melumpuhkannya » . Tapi OP menanyakan alasan penggunaan Optional, bukan alasan menentangnya atau kekurangan desain.
walen
1
Employee employee = Optional.ofNullable(employeeService.getEmployee());Dalam cuplikan ketiga Anda, bukankah seharusnya tipenya Optional<Employee>?
Charlie Harding
2
@ Imibis Dengan cara apa lumpuh? Alasan utama untuk digunakan getadalah jika Anda harus berinteraksi dengan beberapa kode lama. Saya tidak dapat memikirkan banyak alasan valid dalam kode baru yang pernah digunakan get. Yang mengatakan ketika berinteraksi dengan kode warisan seringkali tidak ada alternatif, tetapi masih walen memiliki titik bahwa keberadaan getpemula menyebabkan menulis kode yang buruk.
Voo
1
@ imibis saya tidak menyebutkan Mapsatu kali dan saya tidak tahu ada orang yang membuat perubahan kompatibel non-mundur drastis, jadi pastikan Anda bisa dengan sengaja mendesain API yang buruk Optional- tidak yakin apa yang terbukti. Sebuah Optionalakan masuk akal untuk beberapa tryGetmetode sekalipun.
Voo
2
@Vo Mapadalah contoh yang dikenal semua orang. Tentu saja mereka tidak dapat membuat Map.getpengembalian Opsional karena kompatibilitas ke belakang, tetapi Anda dapat dengan mudah membayangkan kelas seperti Peta lain yang tidak memiliki kendala kompatibilitas seperti itu. Katakan class UserRepository {Optional<User> getUserDetails(int userID) {...}}. Sekarang Anda ingin mendapatkan detail pengguna yang masuk, dan Anda tahu mereka ada karena mereka berhasil masuk dan sistem Anda tidak pernah menghapus pengguna. Jadi kamu lakukan theUserRepository.getUserDetails(loggedInUserID).get().
immibis
12

Poin utama bagi saya dalam menggunakan Opsional adalah tetap jelas ketika pengembang perlu memeriksa apakah fungsi mengembalikan nilai atau tidak.

//service 1
Optional<Employee> employeeOptional = employeeService.getEmployee();
if(employeeOptional.isPresent()){
    Employee employeeOptional= employeeOptional.get();
    System.out.println(employee.getId());
}

//service 2
Employee employee = employeeService.getEmployeeXTPO();
System.out.println(employee.getId());
Rodrigo Menezes
sumber
8
Ini membingungkan saya bahwa semua hal fungsional yang bagus dengan opsional, meskipun benar-benar baik untuk dimiliki, dianggap lebih penting daripada titik ini di sini. Sebelum java 8, Anda harus menggali melalui Javadoc untuk mengetahui apakah Anda harus mengecek respons dari panggilan. Posting java 8, Anda tahu dari tipe kembali jika Anda bisa mendapatkan "tidak ada" kembali atau tidak.
Buhb
3
Nah, ada masalah "kode lama" di mana hal-hal masih bisa mengembalikan nol ... tapi ya, bisa tahu dari tanda tangan itu bagus.
Haakon Løtveit
5
Ya itu pasti bekerja lebih baik dalam bahasa di mana opsional <T> adalah satu-satunya cara untuk menyatakan bahwa nilai mungkin hilang, yaitu bahasa tanpa ptr nol.
dureuill
8

Kesalahan utama Anda adalah Anda masih berpikir dalam istilah yang lebih prosedural. Ini tidak dimaksudkan sebagai kritik terhadap Anda sebagai pribadi, itu hanya sebuah pengamatan. Berpikir dalam istilah yang lebih fungsional datang dengan waktu dan latihan, dan oleh karena itu metodenya hadir dan terlihat seperti hal-hal yang paling jelas benar untuk memanggil Anda. Kesalahan kecil sekunder Anda adalah membuat Opsional di dalam metode Anda. Opsional dimaksudkan untuk membantu mendokumentasikan bahwa sesuatu mungkin atau tidak dapat mengembalikan nilai. Anda mungkin tidak mendapatkan apa-apa.

Ini telah menyebabkan Anda menulis kode yang terbaca dengan sempurna yang tampaknya masuk akal, tetapi Anda tergoda oleh temptresses kembar yang kejam yang didapat dan hadir.

Tentu saja pertanyaannya dengan cepat menjadi "mengapa ada dan hadir di sana?"

Satu hal yang banyak orang lewatkan adalah isPresent () adalah bahwa itu bukan sesuatu yang dibuat untuk kode baru yang ditulis oleh orang-orang sepenuhnya di papan dengan bagaimana lambda sangat berguna, dan yang menyukai hal fungsional.

Namun demikian, memberi kami beberapa (dua) manfaat (?) Baik, bagus, glamourous:

  • Ini memudahkan transisi kode lama untuk menggunakan fitur-fitur baru.
  • Ini memudahkan kurva belajar Opsional.

Yang pertama agak sederhana.

Bayangkan Anda memiliki API yang terlihat seperti ini:

public interface SnickersCounter {
  /** 
   * Provides a proper count of how many snickers have been consumed in total.
   */
  public SnickersCount howManySnickersHaveBeenEaten();

  /**
    * returns the last snickers eaten.<br>
    * If no snickers have been eaten null is returned for contrived reasons.
    */
  public Snickers lastConsumedSnickers();
}

Dan Anda memiliki kelas lama menggunakan ini seperti itu (isi bagian yang kosong):

Snickers lastSnickers = snickersCounter.lastConsumedSnickers();
if(null == lastSnickers) {
  throw new NoSuchSnickersException();
}
else {
  consumer.giveDiabetes(lastSnickers);
}

Contoh dibuat-buat untuk memastikan. Tapi bersabarlah di sini.

Java 8 sekarang telah diluncurkan dan kami berusaha keras untuk naik. Jadi salah satu hal yang kami lakukan adalah kami ingin mengganti antarmuka lama kami dengan sesuatu yang mengembalikan Opsional. Mengapa? Karena seperti orang lain telah dengan anggun menyebutkan: Ini mengeluarkan dugaan apakah sesuatu bisa batal atau tidak. Ini sudah ditunjukkan oleh orang lain. Tapi sekarang kita punya masalah. Bayangkan kita punya (maafkan saya ketika saya menekan alt + F7 pada metode yang tidak bersalah), 46 tempat di mana metode ini disebut dalam kode warisan yang teruji dengan baik yang melakukan pekerjaan yang sangat baik. Sekarang Anda harus memperbarui semua ini.

INI adalah tempat dimana Hadir bersinar.

Karena sekarang: Snickers lastSnickers = snickersCounter.lastConsumedSnickers (); if (null == lastSnickers) {melempar NoSuchSnickersException baru (); } else {consumer.giveDiabetes (lastSnickers); }

menjadi:

Optional<Snickers> lastSnickers = snickersCounter.lastConsumedSnickers();
if(!lastSnickers.isPresent()) {
  throw new NoSuchSnickersException();
}
else {
  consumer.giveDiabetes(lastSnickers.get());
}

Dan ini adalah perubahan sederhana yang dapat Anda berikan kepada junior baru: Dia dapat melakukan sesuatu yang bermanfaat, dan dia akan menjelajahi basis kode pada saat yang sama. menang-menang. Bagaimanapun, sesuatu yang mirip dengan pola ini cukup luas. Dan sekarang Anda tidak perlu menulis ulang kode untuk menggunakan lambdas atau apa pun. (Dalam kasus khusus ini akan sepele, tetapi saya meninggalkan contoh pemikiran di mana itu akan sulit sebagai latihan untuk pembaca.)

Perhatikan bahwa ini berarti cara Anda melakukannya pada dasarnya adalah cara untuk menangani kode lawas tanpa melakukan penulisan ulang yang mahal. Jadi bagaimana dengan kode baru?

Nah, dalam kasus Anda, di mana Anda hanya ingin mencetak sesuatu, Anda cukup melakukan:

snickersCounter.lastConsumedSnickers (). ifPresent (System.out :: println);

Yang cukup sederhana, dan sangat jelas. Titik yang perlahan menggelegak ke permukaan kemudian, adalah bahwa ada kasus penggunaan untuk get () dan isPresent (). Mereka ada di sana untuk memungkinkan Anda memodifikasi kode yang ada secara mekanis untuk menggunakan tipe yang lebih baru tanpa terlalu memikirkannya. Karena itu, apa yang Anda lakukan salah arah dengan cara berikut:

  • Anda memanggil metode yang dapat mengembalikan nol. Gagasan yang benar adalah bahwa metode mengembalikan nol.
  • Anda menggunakan metode bandaid lawas untuk menangani opsi ini, alih-alih menggunakan metode baru yang lezat yang mengandung keanggunan lambda.

Jika Anda ingin menggunakan Opsional sebagai pemeriksaan keamanan-nol sederhana, yang harus Anda lakukan hanyalah ini:

new Optional.ofNullable(employeeServive.getEmployee())
    .map(Employee::getId)
    .ifPresent(System.out::println);

Tentu saja, versi tampan ini terlihat seperti:

employeeService.getEmployee()
    .map(Employee::getId)
    .ifPresent(System.out::println);

Ngomong-ngomong, meskipun tidak diperlukan, saya merekomendasikan penggunaan baris baru per operasi, sehingga lebih mudah dibaca. Mudah dibaca dan dimengerti ketukan setiap hari dalam seminggu.

Ini tentu saja merupakan contoh yang sangat sederhana di mana mudah untuk memahami semua yang kami coba lakukan. Tidak selalu sesederhana ini di kehidupan nyata. Tetapi perhatikan bagaimana dalam contoh ini, apa yang kami ungkapkan adalah niat kami. Kami ingin MENDAPATKAN karyawan, DAPATKAN ID-nya, dan jika mungkin, cetaklah. Ini adalah kemenangan besar kedua dengan Opsional. Itu memungkinkan kita membuat kode yang lebih jelas. Saya juga berpikir bahwa melakukan hal-hal seperti membuat metode yang melakukan banyak hal sehingga Anda dapat memasukkannya ke peta secara umum adalah ide yang bagus.

Haakon Løtveit
sumber
1
Masalahnya di sini adalah bahwa Anda mencoba membuatnya terdengar seperti versi fungsional ini sama terbaca dengan versi yang lebih tua, dalam satu kasus bahkan mengatakan contoh itu "sangat jelas." Bukan itu masalahnya. Contoh itu jelas, tetapi tidak sepenuhnya sempurna , karena membutuhkan lebih banyak pemikiran. map(x).ifPresent(f)sama sekali tidak masuk akal yang sama dengan intuisi if(o != null) f(x). The ifVersi sangat jelas. Ini adalah kasus lain dari orang-orang yang menggunakan kereta musik populer, * bukan * kasus kemajuan nyata.
Aaron
1
Perhatikan bahwa saya tidak mengatakan tidak ada manfaat dalam penggunaan pemrograman fungsional atau Optional. Saya hanya merujuk pada penggunaan opsional dalam kasus khusus ini, dan lebih-lebih pada keterbacaan, karena banyak orang bertindak seperti format ini sama atau lebih mudah dibaca daripada if.
Aaron
1
Itu tergantung pada pengertian kita tentang kejelasan. Meskipun Anda membuat poin yang sangat baik, saya juga ingin menunjukkan bahwa bagi banyak orang lambdas adalah hal yang baru dan aneh. Saya berani bertaruh cookie internet yang oleh beberapa orang dikatakan hadir dan dapatkan dan merasakan kelegaan yang menyenangkan: Mereka sekarang bisa melanjutkan dengan memperbarui kode warisan dan kemudian mempelajari sintaks lambda nanti. Di sisi lain, orang-orang dengan Lisp, Haskell atau pengalaman bahasa yang serupa mungkin senang bahwa mereka sekarang dapat menerapkan pola-pola yang sudah dikenal untuk masalah mereka di java ...
Haakon Løtveit
@ Harun - kejelasan bukan poin dari tipe opsional. Saya pribadi menemukan bahwa mereka tidak mengurangi kejelasan, dan ada beberapa kasus (meskipun bukan kasus ini) di mana mereka meningkatkannya, tetapi manfaat utama adalah bahwa (selama Anda menghindari penggunaan isPresentdan getsebanyak mungkin secara realistis) mereka meningkatkan keamanan . Lebih sulit untuk menulis kode yang salah yang gagal memeriksa tidak adanya nilai jika jenisnya Optional<Whatever>bukan menggunakan nullable Whatever.
Jules
Bahkan jika Anda segera mengambil nilai-nilai, selama Anda tidak menggunakanget , Anda terpaksa memilih apa yang harus dilakukan ketika nilai yang hilang terjadi: Anda akan menggunakan orElse()(atau orElseGet()) untuk memasok default, atau orElseThrow()melempar yang dipilih pengecualian yang mendokumentasikan sifat masalah , yang lebih baik daripada NPE generik yang tidak berarti sampai Anda menggali jejak stack.
Jules
0

Saya tidak melihat manfaat dalam Gaya 2. Anda masih perlu menyadari bahwa Anda memerlukan pemeriksaan nol dan sekarang lebih besar, sehingga kurang dapat dibaca.

Gaya yang lebih baik adalah, jika employeeServive.getEmployee () akan mengembalikan Opsional dan kemudian kode tersebut menjadi:

Optional<Employee> employeeOptional = employeeServive.getEmployee();
if(employeeOptional.isPresent()){
    Employee employee= employeeOptional.get();
    System.out.println(employee.getId());
}

Dengan cara ini Anda tidak dapat memanggilemployeeServive.getEmployee (). GetId () dan Anda melihat bahwa Anda harus menangani valoue opsional entah bagaimana. Dan jika dalam seluruh metode basis kode Anda tidak pernah mengembalikan nol (atau hampir tidak pernah dengan terkenal ke pengecualian tim Anda untuk aturan ini), itu akan meningkatkan keamanan terhadap NPE.

pengguna470365
sumber
12
Opsional menjadi komplikasi tidak berguna saat Anda menggunakan isPresent(). Kami menggunakan opsional sehingga kami tidak perlu menguji.
candied_orange
2
Seperti kata orang lain, jangan gunakan isPresent(). Ini adalah pendekatan yang salah untuk opsional. Gunakan mapatau flatMapsebagai gantinya.
Andres F.
1
@CandiedOrange Namun jawaban terpilih (mengatakan untuk menggunakan ifPresent) hanyalah cara lain untuk menguji.
immibis
1
@CandiedOrange ifPresent(x)sama seperti ujian if(present) {x}. Nilai pengembalian tidak relevan.
immibis
1
@CandiedOrange Meskipun demikian, Anda awalnya berkata "jadi kami tidak perlu menguji." Anda tidak dapat mengatakan "jadi kami tidak perlu menguji" ketika Anda, pada kenyataannya, masih menguji ... yang Anda.
Aaron
0

Saya pikir manfaat terbesar adalah bahwa jika tanda tangan metode Anda mengembalikan EmployeeAnda tidak memeriksa nol. Anda tahu dari tanda tangan itu bahwa dijamin akan mengembalikan seorang karyawan. Anda tidak perlu menangani kasus kegagalan. Dengan opsional, Anda tahu Anda melakukannya.

Dalam kode yang saya lihat ada cek nol yang tidak akan pernah gagal karena orang tidak ingin melacak kode untuk menentukan apakah nol itu mungkin, sehingga mereka melempar cek nol ke mana-mana secara defensif. Ini membuat kode sedikit lebih lambat, tetapi yang lebih penting itu membuat kode lebih ribut.

Namun, agar ini berfungsi, Anda perlu menerapkan pola ini secara konsisten.

Kereta luncur
sumber
1
Cara sederhana untuk mencapai ini adalah menggunakan @Nullabledan @ReturnValuesAreNonnullByDefaultbersama dengan analisis statis.
maaartinus
@maaartinus Menambahkan alat tambahan dalam bentuk analisis statis dan dokumentasi dalam anotasi dekorator, apakah lebih mudah daripada mengkompilasi dukungan API waktu?
Kereta luncur
Menambahkan tooling tambahan memang lebih sederhana, karena tidak memerlukan perubahan kode. Selain itu, Anda juga ingin memiliki karena menangkap bug lain juga. +++ saya menulis naskah sepele menambahkan package-info.javadengan @ReturnValuesAreNonnullByDefaultsemua paket saya, sehingga semua harus saya lakukan adalah untuk menambahkan @Nullabledi mana Anda harus beralih ke Optional. Ini berarti lebih sedikit karakter, tidak ada sampah yang ditambahkan, dan tidak ada runtime-overhead. Saya tidak perlu melihat dokumentasi seperti yang ditunjukkan ketika melayang di atas metode. Alat analisis statis menangkap kesalahan dan kemudian perubahan nullability.
maaartinus
Kekhawatiran terbesar saya Optionaladalah menambahkan cara alternatif untuk menangani nullability. Jika itu di Jawa sejak awal, mungkin baik-baik saja, tapi sekarang itu hanya sakit. Pergi jalan Kotlin akan menjadi IMHO jauh lebih baik. +++ Catatan yang List<@Nullable String>masih daftar string, sementara List<Optional<String>>tidak.
maaartinus
0

Jawaban saya adalah: Jangan. Setidaknya pikirkan dua kali tentang apakah itu benar-benar perbaikan.

Ekspresi (diambil dari jawaban lain) seperti

Optional.of(employeeService)                 // definitely have the service
        .map(EmployeeService::getEmployee)   // getEmployee() might return null
        .map(Employee::getId)                // get ID from employee if there is one
        .ifPresent(System.out::println);     // and if there is an ID, print it

tampaknya menjadi cara fungsional yang tepat untuk menangani metode pengembalian yang semula nullable. Kelihatannya bagus, tidak pernah melempar dan tidak pernah membuat Anda berpikir untuk menangani kasus "absen".

Itu terlihat lebih baik daripada cara gaya lama

Employee employee = employeeService.getEmployee();
if (employee != null) {
    ID id = employee.getId();
    if (id != null) {
        System.out.println(id);
    }
}

yang membuatnya jelas bahwa mungkin ada elseklausa.

Gaya fungsional

  • terlihat sangat berbeda (dan tidak dapat dibaca untuk orang yang tidak terbiasa)
  • adalah sakit untuk di-debug
  • tidak dapat dengan mudah diperpanjang untuk menangani kasus "absen" ( orElsetidak selalu memadai)
  • menyesatkan untuk mengabaikan kasus "absen"

Dalam hal apapun itu tidak harus digunakan sebagai obat mujarab. Jika itu berlaku secara universal, maka semantik .operator harus diganti dengan semantik ?.operator , NPE dilupakan dan semua masalah diabaikan.


Sementara menjadi penggemar Java yang besar, saya harus mengatakan bahwa gaya fungsional hanyalah pengganti pernyataan orang Jawa yang buruk

employeeService
.getEmployee()
?.getId()
?.apply(id => System.out.println(id));

terkenal dari bahasa lain. Apakah kita setuju dengan pernyataan ini?

  • tidak (benar-benar) fungsional?
  • sangat mirip dengan kode gaya lama, hanya jauh lebih sederhana?
  • jauh lebih mudah dibaca daripada gaya fungsional?
  • apakah ramah debugger?
maaartinus
sumber
Anda tampaknya menggambarkan implementasi C # dari konsep ini dengan fitur bahasa yang sama sekali berbeda dengan implementasi Java dengan kelas pustaka inti. Mereka terlihat sama bagi saya
Caleth
@Caleth Tidak mungkin. Kita dapat berbicara tentang "konsep menyederhanakan evaluasi nol-aman", tetapi saya tidak akan menyebutnya "konsep". Kita dapat mengatakan bahwa Java mengimplementasikan hal yang sama menggunakan kelas library inti, tetapi itu hanya berantakan. Mulai saja dengan employeeService.getEmployee().getId().apply(id => System.out.println(id));dan buat null-safe sekali menggunakan operator navigasi Aman dan sekali menggunakan Optional. Tentu, kita bisa menyebutnya "detail implementasi", tetapi implementasi penting. Ada kasus-kasus ketika para lama membuat kode lebih sederhana dan lebih baik, tetapi di sini hanya sebuah penyalahgunaan yang buruk.
maaartinus
Perpustakaan kelas: Optional.of(employeeService).map(EmployeeService::getEmployee).map(Employee::getId).ifPresent(System.out::println);Fitur Bahasa: employeeService.getEmployee()?.getId()?.apply(id => System.out.println(id));. Dua sintaks, tetapi akhirnya tampak sangat mirip dengan saya. Dan "konsep" adalah Optionalalias NullablealiasMaybe
Caleth
Saya tidak mengerti mengapa Anda berpikir salah satunya adalah "penyalahgunaan jelek", dan yang lainnya bagus. Saya bisa mengerti Anda berpikir baik "pelecehan jelek" atau keduanya baik.
Caleth
@Caleth Satu perbedaan adalah bahwa alih-alih menambahkan dua tanda tanya, Anda harus menulis ulang semuanya. Masalahnya bukan bahwa fitur Bahasa dan kode kelas Perpustakaan sangat berbeda. Tidak apa-apa. Masalahnya adalah bahwa kode asli dan kode kelas Perpustakaan sangat berbeda. Gagasan yang sama, kode yang berbeda. Sayang sekali. +++Apa yang disalahgunakan adalah lamdas untuk menangani nullability. Lamdas baik-baik saja, tetapi Optionaltidak. Nullability hanya membutuhkan dukungan bahasa yang tepat seperti di Kotlin. Masalah lain: List<Optional<String>>tidak berhubungan dengan List<String>, di mana kita membutuhkan mereka untuk berhubungan.
maaartinus
0

Opsional adalah konsep (tipe urutan lebih tinggi) yang berasal dari pemrograman fungsional. Menggunakannya tidak jauh dengan pemeriksaan null bersarang dan menyelamatkan Anda dari piramida malapetaka.

Petras Purlys
sumber