Mengambil nilai tanpa harus membatalkan centang di Java

15

Banyak kali saya menemukan diri saya nol memeriksa ketika mengambil nilai dari beberapa hierarki data untuk menghindari NullPointerExceptions, yang saya temukan cenderung rentan terhadap kesalahan dan membutuhkan banyak boilerplate.

Saya telah menulis rutin yang sangat sederhana yang memungkinkan saya melewatkan pemeriksaan nol saat mengambil objek ...

public final class NoNPE {

    public static <T> T get(NoNPEInterface<T> in) {
        try {
            return in.get();
        } catch (NullPointerException e) {
            return null;
        }
    }

    public interface NoNPEInterface<T> {
        T get();
    }
}

Saya menggunakannya agak seperti ini ...

Room room = NoNPE.get(() -> country.getTown().getHouses().get(0).getLivingRoom());

Di atas mengakibatkan saya mendapatkan objek Kamar atau nol, tanpa harus memeriksa semua level orangtua nol.

Apa pendapat Anda tentang hal di atas? Apakah saya membuat pola yang bermasalah? Apakah ada cara yang lebih baik untuk melakukan ini menurut Anda?

Eurig Jones
sumber
1
Karena Anda tampaknya menggunakan Java 8, bolehkah saya menyarankan Anda mempertimbangkan mendesain ulang aplikasi Anda untuk digunakan java.util.Optionalalih-alih nol untuk mewakili data yang hilang? Ini menyediakan utilitas praktis baik untuk kasus yang Anda gambarkan maupun kasus di mana Anda ingin melanjutkan dengan data default alih-alih hanya mengembalikan kondisi kegagalan pada akhir rantai ..
Periata Breatta
Saya pikir Anda pada dasarnya telah menemukan kembali Option(atau Maybe) monad :)
Andres F.
Dimungkinkan untuk mengembalikan Opsional daripada T atau nol: dengan cara ini Anda dapat menggunakan metode orElse () secara langsung. 18 bulan kemudian, tetapi bisa membantu seseorang.
Benj
Pendekatan lain disebutkan dalam posting ini illegalargumentexception.blogspot.com/2015/03/... , salah satunya menggunakan perpustakaan bernama kludje yang memiliki sintaks yang sangat menarik
Benj

Jawaban:

13

Solusi Anda sangat cerdas. Masalah yang saya lihat adalah kenyataan bahwa Anda tidak tahu mengapa Anda mendapatkannya null? Apakah itu karena rumah itu tidak memiliki kamar? Apakah karena kota itu tidak memiliki rumah? Apakah itu karena negara itu tidak memiliki kota? Apakah karena ada nulldi posisi 0 koleksi karena kesalahan bahkan ketika ada rumah di posisi 1 dan lebih besar?

Jika Anda menggunakan NonPEkelas secara ekstensif , Anda akan memiliki masalah debugging yang serius. Saya pikir lebih baik untuk mengetahui di mana tepatnya rantai itu rusak daripada diam-diam mendapatkan nullyang bisa menyembunyikan kesalahan yang lebih dalam.

Ini juga melanggar Hukum Demeter :country.getTown().getHouses().get(0).getLivingRoom() . Lebih sering daripada tidak, melanggar beberapa prinsip yang baik membuat Anda harus menerapkan solusi yang tidak lazim untuk memecahkan masalah yang disebabkan oleh melanggar prinsip tersebut.

Rekomendasi saya adalah bahwa Anda menggunakannya dengan hati-hati dan mencoba menyelesaikan cacat desain yang membuat Anda harus mengeluarkan antipattern kereta (jadi Anda tidak harus menggunakan di NonPEmana-mana). Kalau tidak, Anda mungkin memiliki bug yang akan sulit dideteksi.

Tulains Córdova
sumber
Jawaban yang bagus Ya saya tidak akan tahu dari mana saya mendapat null di rantai. Dalam banyak kasus meskipun saya tidak peduli dan tidak harus memeriksa nol berarti kode lebih mudah dibaca dan kurang rentan terhadap kesalahan boilerplate. Tapi ya Anda benar dalam beberapa kasus di mana saya perlu membuat keputusan logis yang berbeda jika objek orangtua adalah nol, maka ini akan menyebabkan masalah. Metode konvensional atau kelas opsional mungkin merupakan solusi yang lebih aman di sana.
Eurig Jones
Secara umum, saat menggunakan Optionmonad, Anda tidak peduli di mana dalam rantai nilai tidak ada. Saat Anda peduli, Anda mungkin akan menggunakan tipe yang berbeda, seperti Either.
Andres F.
Pendekatan OP mirip dengan C # 6 ?.dan ?[]operator. Salah satu contoh ketika Anda mungkin ingin menggunakan hal seperti itu adalah pengaturan sisi server hirarkis. var shouldDoThing = settings?.a?.b?.c ?? defaultSetting;Siapa yang peduli mengapa ada bagian yang batal? Mungkin Anda tidak dapat mengambil pengaturan. Mungkin Anda memutuskan untuk menghapus bagian dari pengaturan. Bagaimanapun, Anda tidak pernah bisa benar-benar mengandalkan untuk mendapatkan pengaturan server, jadi default biasanya adalah ide yang baik, dan Anda tidak akan peduli mengapa Anda tidak bisa mendapatkan pengaturan yang sebenarnya kecuali itu terjadi sangat sering ketika seharusnya tidak .
chris
Sekarang saya tidak mengatakan itu benar-benar lebih baik atau lebih buruk daripada melokalisasi default dan hanya mendapatkan kembali nilai yang Anda inginkan melalui akses normal settings.a.b.c. Kemudian lagi, ini adalah contoh tunggal yang terisolasi.
chris
10

Idenya baik-baik saja, sebenarnya sangat bagus. Karena Java 8 Optionaljenis ada, penjelasan rinci dapat ditemukan di tipe Java Opsional . Contoh dengan apa yang Anda poskan

Optional.ofNullable(country)
    .map(Country::getTown)
    .map(Town::Houses);

Dan selanjutnya.

J. Pichardo
sumber
1
Ya saya mengetahui kelas Opsional, baik dari Java 8 dan Jambu dan mereka sangat berguna. Tetapi Anda tidak bisa hanya mengambil objek karena Anda biasanya akan membuat kode sedikit lebih sulit untuk dibaca dan sedikit performan juga. Tetapi kelebihannya adalah ada banyak operator yang sangat berguna yang disediakan oleh kelas Opsional.
Eurig Jones
3
@ EJonesJones Saya tidak berpikir kode menjadi kurang performan. Keterbacaan di mata yang melihatnya, tapi saya berpendapat Optionaladalah solusi yang lebih mudah dibaca dari keduanya, jika hanya karena - tidak seperti proposal Anda - itu adalah ungkapan yang sangat umum . Itu bahkan lebih ringkas daripada milikmu!
Andres F.
0

Metode Anda bekerja cukup baik untuk tujuan yang dimaksudkan, meskipun kembali nullketika Anda mendapatkan NullPointerExceptionsuara seperti desain yang buruk.

Coba hindari nullketika Anda bisa dan hanya melewati mereka ketika mereka mewakili sesuatu atau memiliki makna khusus dan hanya mengembalikan mereka ketika mereka mewakili / berarti sesuatu - jika tidak, Anda harus melempar NullPointerException. Ini menghindari bug dan kebingungan. Jika tidak Objectseharusnya null, NullPointerharus dilempar. Jika suatu objek bisa jadi nulltidak ada yang salah ketika dilewatkan. Jika tidak, metode Anda di atas berfungsi.

Luke Melaia
sumber
0

Saya bisa merasakan sakit Anda, tetapi solusi yang diusulkan adalah ide yang buruk.

  • Jika salah satu getter melempar NPE karena alasan lain, Anda akan mengabaikannya.
  • Ada risiko lambda dalam itu tumbuh menjadi kode yang mengerikan. Misalnya, jika ada persyaratan baru untuk mengembalikan konstanta khusus ketika tidak ada rumah di kota, programmer yang malas dapat memperpanjang lamda, meninggalkan semuanya terbungkus NoNPE.get.
  • Seperti yang sudah disebutkan, Optional.mapadalah apa yang Anda cari.
  • Hukuman untuk membuat instance baru NullPointerException seringkali signifikan. Ini banyak mikrodetik, terutama karena tumpukan panggilan Anda semakin besar. Sulit untuk memprediksi di mana utilitas Anda akan digunakan.

Sebagai catatan, NoNPEInterfaceadalah duplikat dari java.util.function.Supplier.

Dalam beberapa kasus, Anda dapat mempertimbangkan menggunakan utils evaluasi ekspresi yang ada di banyak kerangka kerja (misalnya: EL, SpEL):

evaluateProperty(country, "town.houses[0].livingRoom")
Mateusz Stefek
sumber
Ok untuk templat laman web, tetapi umumnya lambat berkembang (tidak ada pengecekan waktu kompilasi) dan lambat berjalan.
kevin cline