Mengapa harus menggunakan Objects.requireNonNull ()?

214

Saya telah mencatat bahwa banyak metode Java 8 dalam penggunaan Oracle JDK Objects.requireNonNull(), yang secara internal melempar NullPointerExceptionjika objek yang diberikan (argumen) null.

public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}

Namun NullPointerExceptionakan tetap terlempar pula jika suatu nullobjek mengalami dereferensi. Jadi, mengapa orang harus melakukan pemeriksaan dan lemparan nol tambahan ini NullPointerException?

Satu jawaban yang jelas (atau manfaat) adalah membuat kode lebih mudah dibaca dan saya setuju. Saya ingin mengetahui alasan lain untuk menggunakan Objects.requireNonNull()di awal metode ini.

pengguna4686046
sumber
1
Pendekatan untuk pemeriksaan argumen memungkinkan Anda menipu ketika Anda sedang menulis unit test. Spring juga memiliki utilitas seperti itu (lihat docs.spring.io/spring/docs/current/javadoc-api/org/… ). Jika Anda memiliki "jika" dan ingin memiliki cakupan tes unit yang tinggi, Anda harus mencakup kedua cabang: ketika kondisi terpenuhi, dan ketika kondisi tidak terpenuhi. Jika Anda menggunakan Objects.requireNonNull, kode Anda tidak memiliki percabangan sehingga satu lulus uji unit akan memberi Anda cakupan 100% :-)
xorcus

Jawaban:

214

Karena Anda dapat membuat hal-hal eksplisit dengan melakukannya. Suka:

public class Foo {
  private final Bar bar;

  public Foo(Bar bar) {
    Objects.requireNonNull(bar, "bar must not be null");
    this.bar = bar;
  }

Atau lebih pendek:

  this.bar = Objects.requireNonNull(bar, "bar must not be null");

Sekarang Anda tahu :

  • ketika objek Foo berhasil dibuat menggunakannew()
  • maka nya bar lapangan dijamin be non-null.

Bandingkan dengan: Anda membuat objek Foo hari ini, dan besok Anda memanggil metode yang menggunakan bidang itu dan melempar. Kemungkinan besar, Anda tidak akan tahu besok mengapa referensi itu nol kemarin ketika diteruskan ke konstruktor!

Dengan kata lain: dengan menggunakan metode ini secara eksplisit untuk memeriksa referensi yang masuk Anda dapat mengontrol titik waktu ketika pengecualian akan dilemparkan. Dan sebagian besar waktu, Anda ingin gagal secepat mungkin !

Keuntungan utama adalah:

  • seperti yang dikatakan, dikendalikan perilaku
  • debugging lebih mudah - karena Anda muntah dalam konteks pembuatan objek. Pada titik di mana Anda memiliki peluang tertentu, log / jejak Anda memberi tahu Anda apa yang salah!
  • dan seperti yang ditunjukkan di atas: kekuatan sebenarnya dari ide ini terungkap dalam hubungannya dengan bidang terakhir . Karena sekarang semua kode lain di kelas Anda dapat dengan aman menganggap itu barbukan nol - dan dengan demikian Anda tidak memerlukan if (bar == null)pemeriksaan di tempat lain!
GhostCat
sumber
23
Anda dapat membuat kode Anda lebih ringkas dengan menulisthis.bar = Objects.requireNonNull(bar, "bar must not be null");
Kirill Rakhman
3
@KirillRakhman Mengajarkan GhostCat sesuatu yang keren yang tidak saya ketahui -> memenangkan tiket dalam undian berhadiah GhostCat.
GhostCat
1
Saya pikir komentar # 1 di sini adalah manfaat sebenarnya dari Objects.requireNonNull(bar, "bar must not be null");. Terima kasih untuk yang satu ini.
Kawu
1
Yang mana yang pertama, dan mengapa orang "bahasa" harus mengikuti penulis beberapa perpustakaan ?! Mengapa jambu biji tidak mengikuti perilaku bahasa Jawa ?!
GhostCat
1
Itu this.bar = Objects.requireNonNull(bar, "bar must not be null");ok di konstruktor, tetapi berpotensi berbahaya dalam metode lain jika dua atau lebih variabel diatur dalam metode yang sama. Contoh: talkwards.com/2018/11/03/...
Erk
100

Gagal cepat

Kode harus macet sesegera mungkin. Seharusnya tidak melakukan setengah dari pekerjaan dan kemudian membatalkan null dan crash hanya kemudian meninggalkan setengah dari beberapa pekerjaan yang dilakukan menyebabkan sistem berada dalam keadaan tidak valid.

Ini biasa disebut "gagal awal" atau "gagal cepat" .

luk2302
sumber
40

Tapi NullPointerException akan tetap dilemparkan jika objek nol adalah dereferensi. Jadi, mengapa orang harus melakukan pemeriksaan nol ekstra ini dan melempar NullPointerException?

Ini berarti Anda mendeteksi masalah dengan segera dan andal .

Mempertimbangkan:

  • Referensi tidak dapat digunakan sampai nanti dalam metode ini, setelah kode Anda melakukan beberapa efek samping
  • Referensi sama sekali tidak dapat disangkal dalam metode ini
    • Ini dapat diteruskan ke kode yang sama sekali berbeda (yaitu penyebab dan kesalahan berjauhan dalam ruang kode)
    • Ini dapat digunakan jauh kemudian (yaitu penyebab dan kesalahan terpisah jauh dalam waktu)
  • Mungkin digunakan di suatu tempat yang referensi nol adalah valid, tetapi memiliki efek yang tidak diinginkan

.NET membuat ini lebih baik dengan memisahkan NullReferenceException("Anda mendefinisikan nilai nol") dari ArgumentNullException("Anda seharusnya tidak melewatkan nol sebagai argumen - dan itu untuk parameter ini ). Saya berharap Jawa melakukan hal yang sama, tetapi bahkan dengan hanya a NullPointerException, itu masih jauh lebih mudah untuk memperbaiki kode jika kesalahan dilemparkan pada titik paling awal di mana ia dapat dideteksi.

Jon Skeet
sumber
13

Menggunakan requireNonNull()sebagai pernyataan pertama dalam suatu metode memungkinkan untuk mengidentifikasi sekarang / puasa penyebab pengecualian.
Stacktrace menunjukkan dengan jelas bahwa pengecualian dilemparkan segera setelah masuknya metode karena penelepon tidak menghormati persyaratan / kontrak. Melewati suatu nullobjek ke metode lain mungkin memang memprovokasi pengecualian pada suatu waktu, tetapi penyebab masalahnya mungkin lebih rumit untuk dipahami karena pengecualian akan dilemparkan dalam doa khusus pada nullobjek yang mungkin jauh lebih jauh.


Berikut ini adalah contoh nyata dan nyata yang menunjukkan mengapa kita harus memilih gagal cepat secara umum dan lebih khusus menggunakan Object.requireNonNull()atau cara apa pun untuk melakukan pemeriksaan tanpa nol pada parameter yang dirancang bukan null.

Misalkan Dictionarykelas yang membentuk suatu LookupServicedan Listdari Stringyang mewakili kata-kata yang terkandung dalam. Bidang ini dirancang untuk tidak nulldan salah satu dari ini dilewatkan di Dictionary konstruktor.

Sekarang anggaplah implementasi "buruk" Dictionarytanpa nullmemeriksa entri metode (di sini adalah konstruktor):

public class Dictionary {

    private final List<String> words;
    private final LookupService lookupService;

    public Dictionary(List<String> words) {
        this.words = this.words;
        this.lookupService = new LookupService(words);
    }

    public boolean isFirstElement(String userData) {
        return lookupService.isFirstElement(userData);
    }        
}


public class LookupService {

    List<String> words;

    public LookupService(List<String> words) {
        this.words = words;
    }

    public boolean isFirstElement(String userData) {
        return words.get(0).contains(userData);
    }
}

Sekarang, mari kita panggil Dictionarykonstruktor dengan nullreferensi untuk wordsparameter:

Dictionary dictionary = new Dictionary(null); 

// exception thrown lately : only in the next statement
boolean isFirstElement = dictionary.isFirstElement("anyThing");

JVM melempar NPE pada pernyataan ini:

return words.get(0).contains(userData); 
Pengecualian di utas "utama" java.lang.NullPointerException
    di LookupService.isFirstElement (LookupService.java#)
    di Dictionary.isFirstElement (Dictionary.java:15)
    di Dictionary.main (Dictionary.java:22)

Pengecualian dipicu di LookupServicekelas sementara asal usulnya jauh lebih awal ( Dictionarykonstruktor). Itu membuat analisis masalah secara keseluruhan menjadi kurang jelas.
Benarkah words null? Benarkah words.get(0) null? Keduanya? Mengapa yang satu, yang lain atau mungkin keduanya null? Apakah ini merupakan kesalahan pengkodean dalam Dictionary(konstruktor? Metode yang dipanggil?)? Apakah ada kesalahan pengkodean LookupService? (konstruktor? metode yang dipanggil?)?
Akhirnya, kita harus memeriksa lebih banyak kode untuk menemukan asal kesalahan dan dalam kelas yang lebih kompleks bahkan mungkin menggunakan debugger untuk lebih mudah memahami apa yang terjadi.
Tetapi mengapa hal sederhana (kurangnya cek nol) menjadi masalah yang kompleks? Bayangkan itu
Karena kami mengizinkan bug awal / kekurangan dapat diidentifikasi pada kebocoran komponen tertentu pada komponen yang lebih rendah.
LookupService itu bukan layanan lokal tetapi layanan jarak jauh atau perpustakaan pihak ketiga dengan sedikit informasi debug atau bayangkan bahwa Anda tidak memiliki 2 lapisan tetapi 4 atau 5 lapisan objek doa sebelum nullterdeteksi? Masalahnya akan lebih kompleks untuk dianalisis.

Jadi cara untuk mendukung adalah:

public Dictionary(List<String> words) {
    this.words = Objects.requireNonNull(words);
    this.lookupService = new LookupService(words);
}

Dengan cara ini, tidak ada sakit kepala: kami mendapatkan pengecualian yang dilemparkan segera setelah ini diterima:

// exception thrown early : in the constructor 
Dictionary dictionary = new Dictionary(null);

// we never arrive here
boolean isFirstElement = dictionary.isFirstElement("anyThing");
Pengecualian di utas "utama" java.lang.NullPointerException
    di java.util.Objects.requireNonNull (Objects.java:203)
    di com.Dictionary. (Dictionary.java:15)
    di com.Dictionary.main (Dictionary.java:24)

Perhatikan bahwa di sini saya mengilustrasikan masalah dengan konstruktor tetapi pemanggilan metode dapat memiliki kendala periksa nihil yang sama.

davidxxx
sumber
Wow! Penjelasan yang bagus.
Jonathas Nascimento
2

Sebagai catatan, ini gagal cepat sebelum Object#requireNotNulldiimplementasikan sedikit berbeda sebelum java-9 di dalam beberapa kelas jre sendiri. Misalkan kasus:

 Consumer<String> consumer = System.out::println;

Dalam java-8 ini dikompilasi sebagai (hanya bagian yang relevan)

getstatic Field java/lang/System.out
invokevirtual java/lang/Object.getClass

Pada dasarnya operasi sebagai: yourReference.getClass- yang akan gagal jika referensi Anda null.

Banyak hal telah berubah di jdk-9 di mana kode yang sama dikompilasi

getstatic Field java/lang/System.out
invokestatic java/util/Objects.requireNonNull

Atau pada dasarnya Objects.requireNotNull (yourReference)

Eugene
sumber
1

Penggunaan dasar adalah memeriksa dan melempar dengan NullPointerExceptionsegera.

Salah satu alternatif (pintasan) yang lebih baik untuk memenuhi kebutuhan yang sama adalah penjelasan @NonNull oleh lombok.

Supun Wijerathne
sumber
1
Anotasi bukan pengganti untuk metode Objek, mereka bekerja bersama. Anda tidak bisa mengatakan @ NonNull x = mightBeNull, Anda akan mengatakan @ NonNull x = Objects.requireNonNull (mightBeNull, "tak terbayangkan!");
Bill K
@BillK maaf, saya tidak mengerti
Supun Wijerathne
Saya hanya mengatakan bahwa penjelasan Nonnull bekerja dengan requireNonNull, ini bukan alternatif, tetapi mereka bekerja bersama dengan cukup baik.
Bill K
untuk menerapkan sifat gagal cepat, itu kan alternatif? Yh saya setuju ini bukan alternatif untuk tugas.
Supun Wijerathne
Saya kira saya akan memanggil requireNonNull () "konversi" dari @ Nullable ke @ NonNull. Jika Anda tidak menggunakan anotasi, metodenya tidak terlalu menarik (karena yang dilakukannya hanyalah melempar NPE seperti kode yang dilindunginya) - meskipun itu menunjukkan maksud Anda dengan cukup jelas.
Bill K
1

Pengecualian null pointer dilemparkan ketika Anda mengakses anggota objek yang null pada titik kemudian. Objects.requireNonNull()segera memeriksa nilai dan melempar pengecualian secara instan tanpa bergerak maju.

Mirwise Khan
sumber
0

Saya pikir itu harus digunakan dalam copy constructor dan beberapa kasus lain seperti DI yang parameter inputnya adalah objek, Anda harus memeriksa apakah parameternya nol. Dalam keadaan seperti itu, Anda dapat menggunakan metode statis ini dengan mudah.

Yu Chai
sumber
0

Dalam konteks ekstensi kompiler yang menerapkan pemeriksaan Nullability (mis: uber / NullAway ),Objects.requireNonNull harus digunakan agak hemat untuk kesempatan ketika Anda memiliki bidang nullable yang kebetulan Anda ketahui bukan nol pada titik tertentu dalam kode Anda.

Dengan cara ini, ada dua penggunaan utama:

  • Validasi

    • sudah dicakup oleh tanggapan lain di sini
    • periksa runtime dengan overhead dan NPE potensial
  • Penandaan nullability (berubah dari @Nullable ke @Nonnull)

    • penggunaan minimal pemeriksaan runtime yang mendukung pemeriksaan waktu kompilasi
    • hanya berfungsi ketika anotasi sudah benar (diberlakukan oleh kompiler)

Contoh penggunaan penandaan Nullability:

@Nullable
Foo getFoo(boolean getNull) { return getNull ? null : new Foo(); }

// Changes contract from Nullable to Nonnull without compiler error
@Nonnull Foo myFoo = Objects.requireNonNull(getFoo(false));
sudah dioverclock
sumber
0

Selain semua jawaban yang benar:

Kami menggunakannya dalam aliran reaktif. Biasanya NullpointerExceptions yang dihasilkan dibungkus dengan Pengecualian lain tergantung pada kejadian mereka di sungai. Karenanya, nanti kita dapat dengan mudah memutuskan bagaimana menangani kesalahan.

Contoh saja: Bayangkan Anda punya

<T> T parseAndValidate(String payload) throws ParsingException { ... };
<T> T save(T t) throws DBAccessException { ... };

di mana parseAndValidatewrapps NullPointerExceptiondari requireNonNulldalam ParsingException.

Sekarang Anda dapat memutuskan, misalnya kapan akan mencoba lagi atau tidak:

...
.map(this::parseAndValidate)
.map(this::save)
.retry(Retry.<T>allBut(ParsingException.class))

Tanpa pemeriksaan, NullPointerException akan terjadi dalam savemetode, yang akan menghasilkan percobaan ulang tanpa akhir. Bahkan layak, bayangkan berlangganan lama, menambahkan

.onErrorContinue(
    throwable -> throwable.getClass().equals(ParsingException.class),
    parsingExceptionConsumer()
)

Sekarang RetryExhaustExceptionlangganan Anda akan terbunuh.

PeMa
sumber