Bagaimana cara menggunakan anotasi @Nullable dan @Nullull secara lebih efektif?

140

Saya dapat melihat itu @Nullabledan @Nonnullpenjelasan dapat membantu dalam mencegah NullPointerExceptiontetapi mereka tidak menyebar terlalu jauh.

  • Keefektifan anotasi ini menurun sepenuhnya setelah satu tingkat tipuan, jadi jika Anda hanya menambahkan beberapa, mereka tidak menyebar terlalu jauh.
  • Karena anotasi ini tidak ditegakkan dengan baik, ada bahaya menganggap nilai yang ditandai dengan @Nonnulltidak nol dan akibatnya tidak melakukan pemeriksaan nol.

Kode di bawah menyebabkan parameter yang ditandai dengan @Nonnullmenjadi nulltanpa menaikkan keluhan. Itu melempar NullPointerExceptionketika dijalankan.

public class Clazz {
    public static void main(String[] args){
        Clazz clazz = new Clazz();

        // this line raises a complaint with the IDE (IntelliJ 11)
        clazz.directPathToA(null);

        // this line does not
        clazz.indirectPathToA(null); 
    }

    public void indirectPathToA(Integer y){
        directPathToA(y);
    }

    public void directPathToA(@Nonnull Integer x){
        x.toString(); // do stuff to x        
    }
}

Apakah ada cara untuk membuat anotasi ini lebih ketat ditegakkan dan / atau disebarkan lebih lanjut?

Mike Rylander
sumber
1
Saya suka ide @Nullableatau @Nonnull, tetapi jika mereka layak itu sangat "kemungkinan untuk mengumpulkan debat"
Maarten Bodewes
Saya pikir cara untuk pindah ke dunia di mana ini menyebabkan kesalahan atau peringatan kompiler adalah dengan meminta pemain @Nonnullketika memanggil @Nonnullmetode dengan variabel nullable. Tentu saja, casting dengan anotasi tidak dimungkinkan di Java 7, tetapi Java 8 akan menambahkan kemampuan untuk menerapkan anotasi pada penggunaan variabel, termasuk gips. Jadi ini mungkin untuk diterapkan di Jawa 8.
Theodore Murdock
1
@TheodoreMurdock, ya, di Java 8 gips (@NonNull Integer) ysecara sintaksis mungkin, tetapi kompiler tidak diizinkan untuk memancarkan kode byte tertentu berdasarkan anotasi. Untuk pernyataan runtime, metode pembantu kecil sudah cukup seperti yang dibahas di bugs.eclipse.org/442103 (mis., directPathToA(assertNonNull(y))) - tetapi ingatlah, ini hanya membantu untuk gagal dengan cepat. Satu-satunya cara yang aman adalah dengan melakukan pemeriksaan nol yang sebenarnya (ditambah semoga implementasi alternatif di cabang lain).
Stephan Herrmann
1
Akan sangat membantu dalam pertanyaan ini untuk mengatakan yang mana @Nonnulldan yang @NullableAnda bicarakan, karena ada beberapa pengumuman serupa (Lihat pertanyaan ini ). Apakah Anda berbicara tentang anotasi dalam paket javax.annotation?
James Dunn
1
@TJamesBoone Untuk konteks pertanyaan ini tidak masalah, ini tentang cara menggunakan salah satu dari mereka secara efektif.
Mike Rylander

Jawaban:

66

Jawaban singkat: Saya kira penjelasan ini hanya berguna bagi IDE Anda untuk memperingatkan Anda tentang kemungkinan kesalahan pointer nol.

Seperti yang dikatakan dalam buku "Kode Bersih", Anda harus memeriksa parameter metode publik Anda dan juga menghindari memeriksa invarian.

Tip lain yang baik adalah tidak pernah mengembalikan nilai null, tetapi menggunakan Pola Objek Null sebagai gantinya.

Pedro Boechat
sumber
10
Untuk nilai pengembalian yang mungkin kosong, saya sangat menyarankan menggunakan Optionaltipe alih-alih polosnull
Patrick
7
Opsional tidak lebih baik, daripada "null". Opsional # get () melempar NoSuchElementException sementara penggunaan null melempar NullPointerException. Keduanya adalah RuntimeException tanpa deskripsi yang bermakna. Saya lebih suka variabel nullable.
30
4
@ 30thh mengapa Anda menggunakan Optional.get () secara langsung dan bukan Optional.isPresent () atau Optional.map terlebih dahulu?
GauravJ
7
@GauravJ Mengapa Anda menggunakan variabel yang dapat dibatalkan secara langsung dan tidak memeriksa, apakah itu null terlebih dahulu? ;-)
30thh
5
Perbedaan antara Optionaldan nullable dalam kasus ini adalah bahwa Optionallebih baik berkomunikasi bahwa nilai ini sengaja dapat kosong. Tentu saja, ini bukan tongkat ajaib dan dalam runtime itu mungkin gagal persis seperti variabel nullable. Namun, penerimaan API oleh programmer lebih baik Optionalmenurut saya.
user1053510
31

Selain IDE Anda memberi Anda petunjuk ketika Anda meneruskan nullke metode yang mengharapkan argumen tidak menjadi nol, ada keuntungan lebih lanjut:

  • Alat analisis kode statis dapat menguji sama dengan IDE Anda (mis. FindBugs)
  • Anda dapat menggunakan AOP untuk memeriksa pernyataan ini

Ini dapat membantu kode Anda lebih mudah dikelola (karena Anda tidak perlu nullmemeriksa) dan lebih sedikit kemungkinan kesalahan.

Uwe Plonus
sumber
9
Saya bersimpati dengan OP di sini, karena meskipun Anda mengutip dua keuntungan ini, dalam kedua kasus Anda menggunakan kata "bisa." Itu berarti bahwa tidak ada jaminan bahwa pemeriksaan ini akan benar-benar terjadi. Sekarang, perbedaan perilaku itu dapat berguna untuk pengujian yang sensitif terhadap kinerja yang Anda ingin hindari berjalan dalam mode produksi, yang kami miliki assert. Saya menemukan @Nullabledan @Nonnullmenjadi ide yang berguna, tetapi saya ingin lebih banyak kekuatan di belakangnya, daripada kami berhipotesis tentang apa yang bisa dilakukan dengan mereka, yang masih membuka kemungkinan untuk tidak melakukan apa pun dengan mereka.
seh
2
Pertanyaannya adalah dari mana harus memulai. Saat ini aneksasinya opsional. Kadang-kadang saya ingin jika mereka bukan karena dalam beberapa keadaan akan sangat membantu untuk menegakkan mereka ...
Uwe Plonus
Bolehkah saya bertanya apa AOP yang Anda maksud di sini?
Chris.Zou
@ Chris.Zou AOP berarti pemrograman berorientasi aspek, misalnya AspectJ
Uwe Plonus
13

Saya pikir pertanyaan awal ini secara tidak langsung menunjuk ke rekomendasi umum bahwa pemeriksaan null-pointer run-time masih diperlukan, meskipun @NonNull digunakan. Lihat tautan berikut:

Anotasi Tipe Java 8 yang baru

Di blog di atas, disarankan agar:

Anotasi Jenis Opsional bukan pengganti untuk validasi runtime Sebelum Anotasi Tipe, lokasi utama untuk menjelaskan hal-hal seperti nullability atau rentang ada di javadoc. Dengan anotasi Jenis, komunikasi ini masuk ke dalam bytecode untuk verifikasi waktu kompilasi. Kode Anda masih harus melakukan validasi runtime.

Jonathanzh
sumber
1
Dipahami, tetapi pemeriksaan serat default memperingatkan bahwa runtime null check tidak diperlukan, yang pada kesan pertama tampaknya mengecilkan rekomendasi ini.
swooby
1
@swooby Biasanya saya mengabaikan peringatan serat jika saya yakin kode saya benar. Peringatan itu bukan kesalahan.
jonathanzh
12

Mengompilasi contoh asli di Eclipse at compliance 1.8 dan dengan analisis nol berbasis anotasi diaktifkan, kami mendapatkan peringatan ini:

    directPathToA(y);
                  ^
Null type safety (type annotations): The expression of type 'Integer' needs unchecked conversion to conform to '@NonNull Integer'

Peringatan ini dianalogikan dengan peringatan yang Anda dapatkan ketika mencampur kode yang dibuat dengan kode lama menggunakan tipe mentah ("konversi tidak dicentang"). Kami memiliki situasi yang sama persis di sini: metode indirectPathToA()memiliki tanda tangan "lawas" karena tidak menentukan kontrak nol. Alat-alat dapat dengan mudah melaporkan ini, sehingga mereka akan memburu Anda semua gang di mana anotasi nol perlu diperbanyak tetapi belum.

Dan ketika menggunakan pintar @NonNullByDefaultkita bahkan tidak harus mengatakan ini setiap saat.

Dengan kata lain: apakah anotasi nol atau "menyebar sangat jauh" tergantung pada alat yang Anda gunakan, dan seberapa keras Anda memperhatikan semua peringatan yang dikeluarkan oleh alat tersebut. Dengan TYPE_USE null annotations Anda akhirnya memiliki opsi untuk membiarkan alat memperingatkan Anda tentang setiap NPE yang mungkin ada dalam program Anda, karena nullness telah menjadi properti intris dari sistem tipe.

Stephan Herrmann
sumber
8

Saya setuju bahwa anotasi "jangan menyebar terlalu jauh". Namun, saya melihat kesalahan di sisi programmer.

Saya mengerti Nonnullanotasi sebagai dokumentasi. Metode berikut ini menyatakan bahwa diperlukan (sebagai prasyarat) argumen non-nol x.

    public void directPathToA(@Nonnull Integer x){
        x.toString(); // do stuff to x        
    }

Cuplikan kode berikut ini kemudian berisi bug. Metode panggilan directPathToA()tanpa penegakan yang ynon-null (yaitu, itu tidak menjamin prasyarat dari metode yang disebut). Satu kemungkinan adalah menambahkan Nonnullanotasi juga ke indirectPathToA()(menyebarkan prakondisi). Kemungkinan kedua adalah untuk memeriksa nullity yin indirectPathToA()dan menghindari panggilan directPathToA()ketika ynull.

    public void indirectPathToA(Integer y){
        directPathToA(y);
    }
Andres
sumber
1
Menyebarkan @Nonnull memiliki indirectPathToA(@Nonnull Integer y)IMHO praktik yang buruk: Anda akan perlu untuk mainain propagasi pada panggilan tumpukan penuh (jadi jika Anda menambahkan nullcek di directPathToA(), Anda akan harus mengganti @Nonnulldengan @Nullabledalam panggilan tumpukan penuh). Ini akan menjadi upaya pemeliharaan besar untuk aplikasi besar.
Julien Kronegg
@Nonnull annotation hanya menekankan bahwa verifikasi nol argumen ada di pihak Anda (Anda harus menjamin bahwa Anda memberikan nilai bukan nol). Ini bukan tanggung jawab metode ini.
Alexander Drobyshevsky
@Nonnull juga merupakan hal yang masuk akal ketika nilai nol tidak masuk akal untuk metode ini
Alexander Drobyshevsky
5

Apa yang saya lakukan dalam proyek saya adalah untuk mengaktifkan opsi berikut dalam inspeksi kode "Kondisi & pengecualian konstan":
Sarankan @ anotasi yang dapat dihapus untuk metode yang dapat mengembalikan nol dan melaporkan nilai yang dapat diupgrade yang dilewatkan ke parameter yang tidak dianotasi Inspeksi

Saat diaktifkan, semua parameter yang tidak dianotasi akan diperlakukan sebagai bukan nol dan karenanya Anda juga akan melihat peringatan pada panggilan tidak langsung Anda:

clazz.indirectPathToA(null); 

Untuk pemeriksaan yang lebih kuat, Kerangka Pemeriksa mungkin merupakan pilihan yang baik (lihat tutorial yang bagus ini .
Catatan : Saya belum menggunakannya dan mungkin ada masalah dengan kompiler Jack: lihat laporan bug ini

TmTron
sumber
4

Di Jawa saya akan menggunakan jenis Opsional Guava . Menjadi tipe aktual Anda mendapatkan jaminan kompilator tentang penggunaannya. Sangat mudah untuk mem-bypassnya dan mendapatkan NullPointerException, tetapi setidaknya tanda tangan dari metode tersebut dengan jelas mengomunikasikan apa yang diharapkan sebagai argumen atau apa yang mungkin kembali.

Ionuț G. Stan
sumber
16
Anda harus berhati-hati dengan ini. Opsional hanya boleh digunakan di mana nilai benar-benar opsional, dan ketiadaan yang digunakan sebagai gerbang keputusan untuk logika lebih lanjut. Saya telah melihat ini disalahgunakan dengan penggantian blanket Objects dengan Opsional dan nol cek diganti dengan cek untuk kehadiran yang melenceng.
Christopher Perry
jika Anda menargetkan JDK 8 atau lebih baru, lebih suka menggunakan java.util.Optionaldaripada kelas Guava. Lihat catatan / perbandingan Guava untuk perincian tentang perbedaan.
AndrewF
1
"nol cek diganti dengan memeriksa kehadiran yang meleset titik" bisa Anda menguraikan, kemudian, apa intinya adalah ? Ini bukan satu - satunya alasan untuk Opsional, menurut pendapat saya, tetapi ini jelas merupakan yang terbesar dan terbaik.
scubbo
4

Jika Anda menggunakan Kotlin, mendukung anotasi nullability ini di kompilernya dan akan mencegah Anda meneruskan null ke metode java yang memerlukan argumen non-nol. Meskipun pertanyaan ini awalnya ditargetkan di Jawa, saya menyebutkan fitur Kotlin ini karena secara khusus ditargetkan pada anotasi Java ini dan pertanyaannya adalah "Apakah ada cara untuk membuat anotasi ini lebih ketat ditegakkan dan / atau disebarkan lebih lanjut?" dan fitur ini membuat penjelasan ini lebih ketat .

Kelas Java menggunakan @NotNullanotasi

public class MyJavaClazz {
    public void foo(@NotNull String myString) {
        // will result in an NPE if myString is null
        myString.hashCode();
    }
}

Kelas Kotlin memanggil kelas Java dan meneruskan null untuk argumen yang dijelaskan dengan @NotNull

class MyKotlinClazz {
    fun foo() {
        MyJavaClazz().foo(null)
    }
}  

Kesalahan kompilator Kotlin menegakkan @NotNullanotasi.

Error:(5, 27) Kotlin: Null can not be a value of a non-null type String

lihat: http://kotlinlang.org/docs/reference/java-interop.html#nullability-annotations

Mike Rylander
sumber
3
Pertanyaannya alamat Java, per tag pertama, dan bukan Kotlin.
seh
1
@ seh lihat pembaruan untuk mengapa jawaban ini relevan dengan pertanyaan ini.
Mike Rylander
2
Cukup adil. Itu fitur yang bagus dari Kotlin. Saya hanya berpikir itu tidak akan memuaskan mereka yang datang ke sini untuk belajar tentang Jawa.
seh
tetapi mengakses myString.hashCode()masih akan melempar NPE bahkan jika @NotNulltidak ditambahkan dalam parameter. Jadi apa yang lebih spesifik tentang menambahkannya?
kAmol
@ kAmol Perbedaannya di sini adalah ketika menggunakan Kotlin, Anda akan mendapatkan kesalahan waktu kompilasi alih-alih kesalahan runtime . Anotasi ini untuk memberi tahu bahwa Anda pengembang perlu memastikan bahwa nol tidak disahkan. Ini tidak akan mencegah null agar tidak diteruskan saat runtime, tetapi itu akan mencegah Anda dari menulis kode yang memanggil metode ini dengan nol (atau dengan fungsi yang dapat mengembalikan null).
Mike Rylander
-4

Karena Java 8 fitur baru Opsional Anda tidak boleh menggunakan @Nullable atau @Notnull dalam kode Anda sendiri lagi . Ambil contoh di bawah ini:

public void printValue(@Nullable myValue) {
    if (myValue != null) {
        System.out.print(myValue);
    } else {
        System.out.print("I dont have a value");
}

Itu dapat ditulis ulang dengan:

public void printValue(Optional<String> myValue) {
    if (myValue.ifPresent) {
        System.out.print(myValue.get());
    } else {
        System.out.print("I dont have a value");
}

Menggunakan opsional memaksa Anda untuk memeriksa nilai nol . Dalam kode di atas, Anda hanya dapat mengakses nilai dengan memanggil getmetode.

Keuntungan lain adalah bahwa kode menjadi lebih mudah dibaca . Dengan tambahan Java 9 ifPresentOrElse , fungsi tersebut bahkan dapat ditulis sebagai:

public void printValue(Optional<String> myValue) {
    myValue.ifPresentOrElse(
        v -> System.out.print(v),
        () -> System.out.print("I dont have a value"),
    )
}
Mathieu Gemard
sumber
Bahkan dengan Optional, masih ada banyak perpustakaan dan kerangka kerja yang menggunakan anotasi ini sehingga tidak layak untuk memperbarui / mengganti semua dependensi Anda dengan versi yang diperbarui untuk menggunakan Opsional. OptionalNamun dapat membantu dalam situasi di mana Anda menggunakan null dalam kode Anda sendiri.
Mike Rylander