Kata pengantar
Pointer nol adalah pengecualian yang paling umum dan menjengkelkan yang kita benci. Untuk mencegah pointer nol dari pengecualian, Anda tidak boleh menulis banyak penilaian non-nol dalam kode Anda.
Java 8 memperkenalkan kelas opsional baru. Untuk menghindari terjadinya pointer nol, tidak perlu menulis sejumlah besar penilaian if(obj!=null) , selama Anda harus memuat data secara opsional, itu adalah wadah yang membungkus objek.
Dikatakan bahwa tidak ada programmer yang mengalami pengecualian pointer nol bukanlah programmer Java, dan NULL memang menyebabkan banyak masalah. Java 8 memperkenalkan kelas baru yang disebut java.util.Optional untuk menghindari banyak masalah yang disebabkan oleh nol.
Mari kita lihat bahaya apa yang bisa disebabkan oleh referensi nol. Pertama buat komputer kelas, struktur ditampilkan pada gambar di bawah ini:
Apa yang terjadi ketika kita memanggil kode berikut?
Versi string = computer.getSoundCard (). GetUsB (). GetVersion ();
Kode di atas tampaknya baik -baik saja, tetapi banyak komputer (seperti Raspberry Pi) sebenarnya tidak memiliki kartu suara, jadi memanggil metode getSoundcard() pasti akan melempar pengecualian pointer nol.
Metode reguler tapi buruk adalah mengembalikan referensi nol untuk menunjukkan bahwa komputer tidak memiliki kartu suara, tetapi ini berarti bahwa metode getUsB () akan dipanggil pada referensi kosong, yang jelas akan melempar pengecualian kontrol selama program berjalan, menyebabkan program berhenti berjalan. Pikirkan tentang hal ini, betapa memalukannya muncul tiba -tiba ketika program Anda berjalan di komputer klien?
Ilmu komputer yang hebat, Tony Hoare pernah menulis: "Saya pikir kutipan nol diciptakan pada tahun 1965, yang menghasilkan kerugian satu miliar dolar. Godaan terbesar bagi saya ketika menggunakan kutipan nol adalah mudah diterapkan."
Jadi bagaimana kita bisa menghindari pengecualian pointer nol saat program berjalan? Anda harus waspada dan terus -menerus memeriksa kemungkinan pointer nol, seperti ini:
Versi String = "Tidak Diketahui"; if (computer! = null) {soundcard soundcard = computer.getSoundCard (); if (soundcard! = null) {usb usb = soundcard.getUsb (); if (usb! = null) {version = usb.getVersion (); }}} Namun, Anda dapat melihat bahwa kode di atas memiliki terlalu banyak pemeriksaan nol dan seluruh struktur kode menjadi sangat jelek. Tetapi kita harus menggunakan penilaian ini untuk memastikan bahwa tidak akan ada pointer nol saat sistem berjalan. Sangat menjengkelkan untuk menilai jika ada banyak referensi kosong seperti itu dalam kode bisnis kami, dan itu juga mengarah pada keterbacaan yang buruk dari kode kami.
Jika Anda lupa untuk memeriksa apakah nilainya kosong, referensi nol juga memiliki masalah potensial yang besar. Dalam artikel ini saya akan membuktikan bahwa menggunakan referensi nol sebagai representasi di mana nilai tidak ada adalah cara yang buruk. Kami membutuhkan model yang lebih baik yang menunjukkan bahwa nilainya tidak ada, daripada menggunakan referensi nol lagi.
Java 8 memperkenalkan kelas baru yang disebut java.util.Optional<T> , yang terinspirasi oleh bahasa Haskell dan bahasa Scala. Kelas ini dapat berisi nilai sewenang -wenang, seperti yang ditunjukkan pada gambar dan kode di bawah ini. Anda dapat menganggap opsional sebagai nilai yang mungkin berisi nilai. Jika opsional tidak mengandung nilai, maka itu kosong, seperti yang ditunjukkan pada gambar di bawah ini.
komputer kelas publik {private opsional <soundcard> SoundCard; Opsional publik <soundcard> getsoundcard () {...} ...} kelas publik SoundCard {private opsional <SUB> USB; PUBLIK OPSIONAL <SUB> getUsB () {...}} kelas publik USB {public String getVersion () {...}} Kode di atas menunjukkan bahwa komputer dapat menggantikan kartu suara (kartu suara mungkin atau mungkin tidak ada). Kartu suara juga dapat menyertakan port USB. Ini adalah metode peningkatan, dan model dapat lebih jelas mencerminkan bahwa nilai yang diberikan mungkin tidak ada.
Tapi bagaimana cara menangani objek Optional<Soundcard> ? Lagi pula, yang ingin Anda dapatkan adalah nomor port USB. Ini sangat sederhana. Kelas opsional berisi beberapa metode untuk menangani apakah nilainya ada. Dibandingkan dengan referensi nol, kelas opsional memaksa Anda untuk menangani apakah nilainya terkait, sehingga menghindari pengecualian pointer nol.
Perlu dicatat bahwa kelas opsional tidak menggantikan referensi nol. Sebaliknya, untuk membuat API yang dirancang lebih mudah dipahami, ketika Anda melihat tanda tangan suatu fungsi, Anda dapat menentukan apakah nilai yang akan diteruskan ke fungsi mungkin tidak ada. Ini meminta Anda untuk membuka kelas opsional untuk menangani nilai aktual.
Mengadopsi mode opsional
Setelah banyak mengatakan, mari kita lihat beberapa kode! Pertama -tama mari kita lihat cara menggunakan opsional untuk menulis ulang deteksi referensi nol tradisional. Di akhir artikel ini Anda akan memahami cara menggunakan opsional.
String name = computer.flatmap (komputer :: getsoundcard) .flatMap (soundcard :: getUsb) .map (usb :: getVersion) .orelse ("tidak diketahui"); Buat objek opsional
Objek opsional kosong dapat dibuat:
Opsional <soundcard> sc = opsional.empty ();
Berikutnya adalah membuat opsional yang berisi nilai non-nol:
Soundcard soundcard = new soundcard (); opsional <soundcard> sc = opsional.of (soundcard);
Jika kartu suara nol, pengecualian pointer nol akan segera dilemparkan (ini lebih baik daripada hanya melemparkannya saat Anda mendapatkan atribut kartu suara).
Dengan menggunakan Ofnullable, Anda dapat membuat objek opsional yang mungkin berisi referensi nol:
Opsional <soundcard> sc = opsional.ofnullable (soundcard);
Jika kartu suara adalah referensi nol, objek opsional kosong.
Pemrosesan nilai secara opsional
Sekarang ada objek opsional, Anda dapat memanggil metode yang sesuai untuk menangani apakah nilai pada objek opsional ada. Dibandingkan dengan deteksi null, kita dapat menggunakan metode ifpresent (), seperti ini:
Opsional <SoundCard> SoundCard = ...; SoundCard.ifpresent (System.out :: println);
Dengan cara ini, tidak perlu melakukan deteksi nol. Jika objek opsional kosong, maka informasi apa pun tidak akan dicetak.
Anda juga dapat menggunakan metode isPresent() untuk melihat apakah objek opsional benar -benar ada. Selain itu, ada juga metode get () yang mengembalikan nilai yang disertakan dalam objek opsional, jika ada. Kalau tidak, NosuchelementException akan dilemparkan. Dua metode ini dapat digunakan bersama seperti berikut untuk menghindari pengecualian:
if (soundcard.ispresent ()) {system.out.println (soundcard.get ());} Namun, metode ini tidak disarankan (tidak memiliki perbaikan dibandingkan dengan deteksi nol). Di bawah ini kita akan membahas cara kerja yang biasa.
Mengembalikan nilai default dan operasi terkait
Saat bertemu NULL, operasi reguler adalah untuk mengembalikan nilai default, yang dapat Anda gunakan ekspresi terner untuk diimplementasikan:
SoundCard SoundCard = MaybesoundCard! = NULL? MaybesoundCard: SoundCard baru ("BASIC_SOUND_CARD"); Jika Anda menggunakan objek opsional, Anda dapat menggunakan orElse() untuk mengganti. Saat opsional orElse() dapat mengembalikan nilai default:
SoundCard SoundCard = maybesoundcard.orelse (SoundCard baru ("DEFAUT")); Demikian pula, ketika opsional kosong, orelsethrow () dapat digunakan untuk melempar pengecualian:
SoundCard SoundCard = maybesoundcard.orelsethrow (ilegalstateException :: new);
Gunakan filter untuk memfilter nilai spesifik
Kami sering menyebut metode objek untuk menilai propertinya. Misalnya, Anda mungkin perlu memeriksa apakah nomor port USB adalah nilai tertentu. Untuk alasan keamanan, Anda perlu memeriksa apakah penggunaan medis yang menunjuk ke USB adalah nol, dan kemudian hubungi metode getVersion() , seperti ini:
Usb usb = ...; if (usb! = Null && "3.0" .equals (usb.getVersion ())) {System.out.println ("ok");} Jika Anda menggunakan opsional, Anda dapat menggunakan fungsi filter untuk menulis ulang:
Opsional <SUB> MaybeUsB = ...; MaybeUsB.Filter (USB -> "3.0" .Equals (usb.getVersion ()) .ifpresent (() -> System.out.println ("ok")); Metode filter membutuhkan predikat yang berlawanan sebagai parameter. Jika nilai dalam opsional ada dan memenuhi prediksi, fungsi filter akan mengembalikan nilai yang memenuhi kondisi tersebut; Kalau tidak, objek opsional kosong akan dikembalikan.
Gunakan metode peta untuk mengekstrak dan mengonversi data
Pola umum adalah mengekstrak beberapa sifat suatu objek. Misalnya, untuk objek kartu suara, Anda mungkin perlu mendapatkan objek USB dan kemudian menentukan nomor versinya. Biasanya implementasi kami seperti ini:
if (soundcard! = null) {usb usb = soundcard.getUsb (); if (usb! = null && "3.0" .equals (usb.getVersion ()) {System.out.println ("ok");}} Kita dapat menggunakan metode peta untuk mengganti deteksi ini nol dan kemudian mengekstrak objek tipe objek.
Opsional <SUB> USB = maybesoundcard.map (SoundCard :: getUsb);
Ini sama dengan menggunakan fungsi peta menggunakan stream. Menggunakan aliran membutuhkan umpan fungsi sebagai parameter ke fungsi peta, dan fungsi yang diteruskan akan diterapkan ke setiap elemen dalam aliran. Saat streaming ruang dan waktu, tidak ada yang terjadi.
Nilai yang terkandung dalam opsional akan dikonversi oleh fungsi yang dilewati (berikut adalah fungsi yang mendapatkan USB dari kartu suara). Jika objek opsional adalah ruang-waktu, tidak ada yang akan terjadi.
Kemudian, kami menggabungkan metode peta dan metode filter untuk memfilter kartu suara dengan nomor versi USB bukan 3.0.
maybesoundcard.map (soundcard :: getUsb) .filter (usb -> "3.0" .Equals (usb.getVersion ()) .ifpresent (() -> System.out.println ("OK")); Dengan cara ini, kode kami mulai terlihat sedikit seperti apa yang kami berikan di awal, tanpa deteksi nol.
Melewati Objek Opsional Menggunakan Fungsi FlatMap
Sekarang, contoh cara refactor kode menggunakan opsional telah diperkenalkan. Jadi bagaimana kita harus mengimplementasikan kode berikut dengan cara yang aman?
Versi string = computer.getSoundCard (). GetUsB (). GetVersion ();
Perhatikan bahwa kode di atas semua mengekstraksi objek lain dari satu objek, yang dapat diimplementasikan menggunakan fungsi peta. Di artikel sebelumnya, kami mengatur objek Optional<Soundcard> di komputer, dan SoundCard berisi objek Optional<USB> , jadi kami dapat memperbaiki kode dengan cara ini
Versi string = computer.map (komputer :: getsoundcard) .map (soundcard :: getUsb) .map (usb :: getVersion) .orelse ("tidak diketahui"); Sayangnya, kode di atas mengkompilasi kesalahan, jadi mengapa? Variabel komputer adalah tipe Optional<Computer> , sehingga tidak ada masalah memanggil fungsi peta. Namun, metode getSoundcard() mengembalikan objek Optional<Soundcard> , yang mengembalikan objek tipe Optional<Optional<Soundcard>> . Setelah fungsi peta kedua dipanggil, panggilan ke fungsi getUSB() menjadi ilegal.
Gambar berikut menjelaskan skenario ini:
Implementasi kode sumber fungsi peta adalah sebagai berikut:
Public <u> opsional <u> peta (fungsi <? Super t ,? Extends u> mapper) {objects.requirenonnull (mapper); if (! isPresent ()) kembali kosong (); else {return opsional.ofnullable (mapper.apply (value)); }} Dapat dilihat bahwa fungsi peta akan menghubungi Optional.ofNullable() lagi, menghasilkan pengembalian Optional<Optional<Soundcard>>
Opsional menyediakan fungsi flatmap, yang dirancang untuk mengubah nilai objek opsional (seperti operasi peta) dan kemudian mengompres opsional dua tingkat menjadi satu. Gambar berikut menunjukkan perbedaan antara objek opsional dalam konversi tipe dengan memanggil peta dan flatmap:
Jadi kita bisa menulis ini:
Versi string = computer.flatmap (komputer :: getsoundcard) .flatMap (soundcard :: getUsb) .map (usb :: getVersion) .orelse ("tidak diketahui"); FlatMap pertama memastikan bahwa pengembalian adalah Optional<Soundcard> daripada Optional<Optional<Soundcard>> , dan flatmap kedua mengimplementasikan fungsi yang sama sehingga pengembalian adalah Optional<USB> . Perhatikan bahwa map() disebut ketiga kalinya, karena getVersion() mengembalikan objek string alih -alih objek opsional.
Kami akhirnya menulis ulang kode jelek cek nol bersarang yang baru saja kami mulai gunakan, yang sangat mudah dibaca, dan juga menghindari terjadinya pengecualian penunjuk nol.
Meringkaskan
Dalam artikel ini, kami mengadopsi kelas baru java.util.Optional<T> Disediakan oleh Java 8. Niat asli dari kelas ini bukan untuk menggantikan referensi nol, tetapi untuk membantu desainer merancang API yang lebih baik. Baca saja tanda tangan fungsi dan ketahui apakah fungsi menerima nilai yang mungkin atau mungkin tidak ada. Selain itu, opsional memaksa Anda untuk menyalakan opsional dan kemudian menangani apakah nilainya ada, yang membuat kode Anda menghindari pengecualian penunjuk nol potensial.
Oke, di atas adalah seluruh konten artikel ini. Saya berharap konten artikel ini memiliki nilai referensi tertentu untuk studi atau pekerjaan semua orang. Jika Anda memiliki pertanyaan, Anda dapat meninggalkan pesan untuk berkomunikasi. Terima kasih atas dukungan Anda ke wulin.com.