Menghindari! = Pernyataan nol

4015

Saya menggunakan object != nullbanyak hal untuk menghindari NullPointerException.

Apakah ada alternatif yang baik untuk ini?

Misalnya saya sering menggunakan:

if (someobject != null) {
    someobject.doCalc();
}

Ini pemeriksaan untuk NullPointerExceptionuntuk someobjectobjek dalam potongan di atas.

Perhatikan bahwa jawaban yang diterima mungkin kedaluwarsa, lihat https://stackoverflow.com/a/2386013/12943 untuk pendekatan yang lebih baru.

Goran Martinic
sumber
120
@ Shervin Mendorong nulls membuat kode kurang dimengerti dan kurang dapat diandalkan.
Tom Hawtin - tackline
44
Operator Elvis diusulkan tetapi sepertinya tidak ada di Java 7 . Sangat buruk, ?. ?: dan? [] adalah penghemat waktu yang luar biasa.
Scott
72
Tidak menggunakan null lebih unggul daripada kebanyakan saran lainnya di sini. Lempar pengecualian, jangan kembali atau mengizinkan nol. BTW - kata kunci 'menegaskan' tidak berguna, karena dinonaktifkan secara default. Gunakan mekanisme kegagalan yang selalu aktif
ianpojman
13
Ini adalah salah satu alasan mengapa saya sekarang menggunakan Scala. Dalam Scala semuanya tidak dapat dibatalkan. Jika Anda ingin mengizinkan untuk meneruskan atau mengembalikan "tidak ada", maka Anda harus menggunakan Opsi [T] secara rumit alih-alih hanya argumen atau jenis pengembalian saja.
Thekwasti
11
@thSoft Memang, Optiontipe Scala yang luar biasa dirusak oleh keengganan bahasa untuk mengontrol atau melarang null. Saya menyebutkan ini di hackernews dan diturunkan dan mengatakan bahwa "toh tidak ada yang menggunakan null di Scala". Coba tebak, saya sudah menemukan nol di Scala yang ditulis rekan kerja. Ya, mereka "melakukan hal yang salah" dan harus dididik tentang hal itu, tetapi faktanya tetap sistem tipe bahasa harus melindungi saya dari ini dan itu tidak :(
Andres F.

Jawaban:

2644

Bagi saya ini terdengar seperti masalah umum yang cenderung dihadapi oleh pengembang menengah ke menengah: mereka tidak tahu atau tidak mempercayai kontrak yang mereka ikuti dan secara defensif memeriksa nol. Selain itu, ketika menulis kode mereka sendiri, mereka cenderung mengandalkan pengembalian nol untuk menunjukkan sesuatu sehingga membutuhkan pemanggil untuk memeriksa nol.

Dengan kata lain, ada dua contoh di mana pemeriksaan nol muncul:

  1. Di mana null adalah respons yang valid dalam hal kontrak; dan

  2. Di mana itu bukan respons yang valid.

(2) mudah. Baik menggunakan assertpernyataan (pernyataan) atau membiarkan kegagalan (misalnya, NullPointerException ). Pernyataan adalah fitur Java yang sangat kurang dimanfaatkan yang ditambahkan pada 1.4. Sintaksnya adalah:

assert <condition>

atau

assert <condition> : <object>

di mana <condition>adalah ekspresi boolean dan <object>merupakan objek yang toString()keluaran metodenya akan dimasukkan dalam kesalahan.

Sebuah assertpernyataan melempar Error( AssertionError) jika kondisi tidak benar. Secara default, Java mengabaikan pernyataan. Anda dapat mengaktifkan pernyataan dengan meneruskan opsi -eake JVM. Anda dapat mengaktifkan dan menonaktifkan pernyataan untuk masing-masing kelas dan paket. Ini berarti bahwa Anda dapat memvalidasi kode dengan pernyataan sambil mengembangkan dan menguji, dan menonaktifkannya dalam lingkungan produksi, meskipun pengujian saya telah menunjukkan hampir tidak ada dampak kinerja dari pernyataan.

Tidak menggunakan pernyataan dalam kasus ini OK karena kode hanya akan gagal, yang akan terjadi jika Anda menggunakan pernyataan. Satu-satunya perbedaan adalah bahwa dengan pernyataan itu mungkin terjadi lebih cepat, dengan cara yang lebih bermakna dan mungkin dengan informasi tambahan, yang dapat membantu Anda untuk mengetahui mengapa hal itu terjadi jika Anda tidak mengharapkannya.

(1) sedikit lebih sulit. Jika Anda tidak memiliki kendali atas kode yang Anda panggil maka Anda mandek. Jika null adalah respons yang valid, Anda harus memeriksanya.

Namun, jika itu kode yang Anda kontrol (dan ini sering terjadi), maka itu adalah cerita yang berbeda. Hindari menggunakan nulls sebagai respons. Dengan metode yang mengembalikan koleksi, mudah: kembalikan koleksi kosong (atau array) alih-alih nulls sepanjang waktu.

Dengan non-koleksi mungkin lebih sulit. Pertimbangkan ini sebagai contoh: jika Anda memiliki antarmuka ini:

public interface Action {
  void doSomething();
}

public interface Parser {
  Action findAction(String userInput);
}

di mana Parser mengambil input pengguna mentah dan menemukan sesuatu untuk dilakukan, mungkin jika Anda mengimplementasikan antarmuka baris perintah untuk sesuatu. Sekarang Anda dapat membuat kontrak yang mengembalikan nol jika tidak ada tindakan yang sesuai. Itu mengarah nol memeriksa Anda bicarakan.

Solusi alternatif adalah tidak pernah mengembalikan null dan sebaliknya menggunakan pola Objek Null :

public class MyParser implements Parser {
  private static Action DO_NOTHING = new Action() {
    public void doSomething() { /* do nothing */ }
  };

  public Action findAction(String userInput) {
    // ...
    if ( /* we can't find any actions */ ) {
      return DO_NOTHING;
    }
  }
}

Membandingkan:

Parser parser = ParserFactory.getParser();
if (parser == null) {
  // now what?
  // this would be an example of where null isn't (or shouldn't be) a valid response
}
Action action = parser.findAction(someInput);
if (action == null) {
  // do nothing
} else {
  action.doSomething();
}

untuk

ParserFactory.getParser().findAction(someInput).doSomething();

yang merupakan desain yang jauh lebih baik karena mengarah ke kode yang lebih ringkas.

Yang mengatakan, mungkin itu sepenuhnya tepat untuk metode findAction () untuk membuang Pengecualian dengan pesan kesalahan yang bermakna - terutama dalam kasus ini di mana Anda mengandalkan input pengguna. Akan jauh lebih baik bagi metode findAction untuk melemparkan Exception daripada metode pemanggilan untuk meledakkan dengan NullPointerException sederhana tanpa penjelasan.

try {
    ParserFactory.getParser().findAction(someInput).doSomething();
} catch(ActionNotFoundException anfe) {
    userConsole.err(anfe.getMessage());
}

Atau jika Anda berpikir mekanisme coba / tangkap terlalu jelek, daripada Jangan Melakukan apa pun, tindakan default Anda harus memberikan umpan balik kepada pengguna.

public Action findAction(final String userInput) {
    /* Code to return requested Action if found */
    return new Action() {
        public void doSomething() {
            userConsole.err("Action not found: " + userInput);
        }
    }
}
cletus
sumber
631
Saya tidak setuju dengan pernyataan Anda untuk tindakan DO_NOTHING. Jika metode tindakan menemukan tidak dapat menemukan tindakan, maka mengembalikan nol adalah hal yang benar untuk dilakukan. Anda telah "menemukan" suatu tindakan dalam kode Anda yang tidak benar-benar ditemukan, yang melanggar prinsip metode ini, untuk menemukan tindakan yang bisa digunakan.
MetroidFan2002
266
Saya setuju bahwa null terlalu sering digunakan di Jawa, terutama dengan daftar. Begitu banyak apis akan lebih baik jika mereka mengembalikan daftar / array / koleksi kosong daripada nol. Sering kali, null digunakan di mana pengecualian harus dibuang. Pengecualian harus dilemparkan jika parser tidak dapat menguraikan.
Laplie Anderson
92
Contoh terakhir di sini adalah, IIRC, pola desain Obyek Null.
Steven Evers
62
@Cshah (dan MetroidFan2002). Sederhana, masukkan itu ke dalam kontrak dan kemudian jelas bahwa tindakan yang dikembalikan tidak ditemukan tidak akan melakukan apa-apa. Jika ini adalah informasi penting bagi penelepon, maka berikan cara untuk menemukan bahwa itu adalah tindakan yang tidak ditemukan (yaitu memberikan metode untuk memeriksa apakah hasilnya adalah objek DO_NOTHING). Atau, jika tindakan biasanya ditemukan maka Anda masih tidak boleh mengembalikan nol tetapi sebaliknya melemparkan pengecualian yang secara khusus menunjukkan kondisi itu - ini masih menghasilkan kode yang lebih baik. Jika diinginkan, berikan metode terpisah untuk mengembalikan boolean untuk memeriksa apakah ada tindakan.
Kevin Brock
35
Ringkas tidak sama dengan kode kualitas. Maaf, kamu pikir begitu. Kode Anda menyembunyikan situasi di mana kesalahan akan menguntungkan.
gshauger
635

Jika Anda menggunakan (atau berencana untuk menggunakan) IDE Java seperti JetBrains IntelliJ IDEA , Eclipse atau Netbeans atau alat seperti findbugs maka Anda dapat menggunakan anotasi untuk menyelesaikan masalah ini.

Pada dasarnya, Anda punya @Nullabledan @NotNull.

Anda dapat menggunakan metode dan parameter, seperti ini:

@NotNull public static String helloWorld() {
    return "Hello World";
}

atau

@Nullable public static String helloWorld() {
    return "Hello World";
}

Contoh kedua tidak akan dikompilasi (dalam IntelliJ IDEA).

Saat Anda menggunakan helloWorld()fungsi pertama di bagian kode yang lain:

public static void main(String[] args)
{
    String result = helloWorld();
    if(result != null) {
        System.out.println(result);
    }
}

Sekarang kompiler IntelliJ IDEA akan memberi tahu Anda bahwa cek tidak berguna, karena helloWorld()fungsinya tidak akan kembali null, selamanya.

Menggunakan parameter

void someMethod(@NotNull someParameter) { }

jika Anda menulis sesuatu seperti:

someMethod(null);

Ini tidak akan dikompilasi.

Contoh terakhir menggunakan @Nullable

@Nullable iWantToDestroyEverything() { return null; }

Melakukan ini

iWantToDestroyEverything().something();

Dan Anda dapat yakin bahwa ini tidak akan terjadi. :)

Ini adalah cara yang bagus untuk membiarkan kompiler memeriksa sesuatu lebih dari biasanya dan untuk menegakkan kontrak Anda menjadi lebih kuat. Sayangnya, itu tidak didukung oleh semua kompiler.

Dalam IntelliJ IDEA 10.5 dan seterusnya, mereka menambahkan dukungan untuk @Nullable @NotNullimplementasi lainnya .

Lihat posting blog Lebih fleksibel dan dapat dikonfigurasi @ Nullable / @ NotNull annotations .

Luca Molteni
sumber
120
@NotNull, @Nullabledan anotasi nullness lainnya adalah bagian dari JSR 305 . Anda juga dapat menggunakannya untuk mendeteksi potensi masalah dengan alat-alat seperti FindBugs .
Jacek S
32
Saya merasa aneh bahwa antarmuka @NotNull& @Nullablehidup dalam paket com.sun.istack.internal. (Saya kira saya mengaitkan com.sun dengan peringatan tentang menggunakan API berpemilik.)
Jonik
20
portabilitas kode menjadi nol dengan jetbrains. Saya akan berpikir dua kali (persegi) sebelum mengikat ke level ide. Seperti Jacek S mengatakan mereka adalah bagian dari JSR dengan cara apa pun yang saya pikir adalah JSR303.
Java Ka Baby
10
Saya benar-benar tidak berpikir bahwa menggunakan kompiler kustom adalah solusi untuk masalah ini.
Shivan Dragon
64
Hal yang baik tentang anotasi, yang @NotNulldan @Nullableadalah bahwa mereka dengan baik menurun ketika kode sumber dibangun oleh sistem yang tidak memahaminya. Jadi, pada dasarnya, argumen bahwa kode ini tidak portabel mungkin tidak valid - jika Anda menggunakan sistem yang mendukung dan memahami anotasi ini, Anda mendapatkan manfaat tambahan dari pengecekan kesalahan yang lebih ketat, jika tidak Anda mendapatkan lebih sedikit tetapi kode Anda tetap harus build fine, dan kualitas program Anda yang berjalan SAMA, karena penjelasan ini tidak diberlakukan saat runtime. Selain itu, semua kompiler adalah custom ;-)
amn
321

Jika nilai nol tidak diizinkan

Jika metode Anda dipanggil secara eksternal, mulailah dengan sesuatu seperti ini:

public void method(Object object) {
  if (object == null) {
    throw new IllegalArgumentException("...");
  }

Kemudian, di sisa metode itu, Anda akan tahu itu objectbukan nol.

Jika ini adalah metode internal (bukan bagian dari API), cukup dokumentasikan bahwa itu tidak boleh nol, dan hanya itu.

Contoh:

public String getFirst3Chars(String text) {
  return text.subString(0, 3);
}

Namun, jika metode Anda hanya meneruskan nilainya, dan metode selanjutnya meneruskannya dll. Itu bisa bermasalah. Dalam hal ini Anda mungkin ingin memeriksa argumen seperti di atas.

Jika nol diizinkan

Ini sangat tergantung. Jika ternyata saya sering melakukan hal seperti ini:

if (object == null) {
  // something
} else {
  // something else
}

Jadi saya bercabang, dan melakukan dua hal yang sangat berbeda. Tidak ada cuplikan kode yang jelek, karena saya benar-benar perlu melakukan dua hal berbeda tergantung pada datanya. Misalnya, apakah saya harus mengerjakan input, atau haruskah saya menghitung nilai default yang baik?


Sebenarnya jarang bagi saya untuk menggunakan idiom " if (object != null && ...".

Mungkin lebih mudah untuk memberi Anda contoh, jika Anda menunjukkan contoh di mana Anda biasanya menggunakan idiom.

myplacedk
sumber
94
Apa gunanya melempar IllegalArgumentException? Saya pikir NullPointerException akan lebih jelas, dan itu juga akan dibuang jika Anda tidak melakukan null-check sendiri. Saya akan menggunakan pernyataan tegas atau tidak sama sekali.
Axel
23
Tidak mungkin setiap nilai selain null dapat diterima. Anda dapat memiliki IllegalArgumentException, OutOfRageException dll. Terkadang ini masuk akal. Di lain waktu Anda akhirnya menciptakan banyak kelas pengecualian yang tidak menambah nilai, maka Anda hanya menggunakan IllegalArgumentException. Tidak masuk akal untuk memiliki satu pengecualian untuk null-input, dan satu lagi untuk yang lainnya.
myplacedk
7
Ya, saya setuju dengan prinsip gagal-cepat, tetapi dalam contoh yang diberikan di atas, nilai tidak diteruskan, tetapi adalah objek yang harus dipanggil metode. Jadi gagal sama-sama cepat, dan menambahkan cek nol hanya untuk melempar pengecualian yang akan dilempar pula pada waktu dan tempat yang sama sepertinya tidak membuat debugging lebih mudah.
Axel
8
Lubang keamanan? JDK penuh dengan kode seperti ini. Jika Anda tidak ingin pengguna melihat stacktraces, cukup nonaktifkan saja. Tidak ada yang menyiratkan bahwa perilaku tersebut tidak didokumentasikan. MySQL ditulis dalam C di mana dereferencing null pointer adalah perilaku yang tidak terdefinisi, tidak seperti melemparkan pengecualian.
fgb
8
throw new IllegalArgumentException("object==null")
Thorbjørn Ravn Andersen
238

Wow, saya hampir benci menambahkan jawaban lain ketika kami memiliki 57 cara berbeda untuk merekomendasikannya NullObject pattern, tapi saya pikir beberapa orang yang tertarik dengan pertanyaan ini mungkin ingin tahu bahwa ada proposal di meja untuk Java 7 untuk menambahkan "null-safe handling " —sederhana sintaksis untuk logika if-not-equal-null.

Contoh yang diberikan oleh Alex Miller terlihat seperti ini:

public String getPostcode(Person person) {  
  return person?.getAddress()?.getPostcode();  
}  

The ?.berarti hanya de-referensi pengenal kiri jika tidak nol, jika tidak mengevaluasi sisa ekspresi sebagai null. Beberapa orang, seperti anggota Jawa Posse, Dick Wall dan para pemilih di Devoxx sangat menyukai proposal ini, tetapi ada juga oposisi, dengan alasan bahwa hal itu akan mendorong lebih banyak penggunaan nullsebagai nilai sentinel.


Update: Sebuah usulan resmi untuk operator null-aman di Jawa 7 telah diserahkan di bawah Proyek Coin. Sintaksnya sedikit berbeda dari contoh di atas, tetapi gagasannya sama.


Pembaruan: Proposal operator nol-aman tidak membuatnya menjadi Project Coin. Jadi, Anda tidak akan melihat sintaks ini di Java 7.

erickson
sumber
20
Saya pikir ini salah. Seharusnya ada cara untuk menentukan bahwa variabel yang diberikan SELALU non-null.
Thorbjørn Ravn Andersen
5
Pembaruan: proposal tidak akan membuat Java7. Lihat blogs.sun.com/darcy/entry/project_coin_final_five .
Boris Terzic
9
Ide yang menarik tetapi pilihan sintaksis tidak masuk akal; Saya tidak ingin basis kode yang penuh tanda tanya disematkan ke setiap sendi.
Rob
7
Operator ini ada di Groovy , jadi mereka yang ingin menggunakannya masih memiliki itu sebagai opsi.
Muhd
8
Ini adalah ide paling cerdik yang pernah saya lihat. Itu harus ditambahkan ke setiap bahasa yang masuk akal dari sintaks C. Saya lebih suka "pin tanda tanya" di mana-mana daripada menelusuri layar penuh garis atau menghindari "klausa penjaga" sepanjang hari.
Victor
196

Jika nilai yang tidak ditentukan tidak diizinkan:

Anda dapat mengkonfigurasi IDE Anda untuk memperingatkan Anda tentang potensi nol dereferencing. Misalnya dalam Eclipse, lihat Preferensi> Java> Kompiler> Analisis Kesalahan / Peringatan / Null .

Jika nilai yang tidak ditentukan diizinkan:

Jika Anda ingin mendefinisikan API baru di mana nilai yang tidak terdefinisi masuk akal , gunakan Pola Opsi (mungkin terbiasa dengan bahasa fungsional). Ini memiliki keuntungan sebagai berikut:

  • Itu dinyatakan secara eksplisit dalam API apakah input atau output ada atau tidak.
  • Compiler memaksa Anda untuk menangani case "undefined".
  • Opsi adalah monad , jadi tidak perlu untuk pengecekan verbose null, cukup gunakan peta / foreach / getOrElse atau kombinator serupa untuk menggunakan nilai (contoh) dengan aman .

Java 8 memiliki Optionalkelas bawaan (disarankan); untuk versi sebelumnya, ada alternatif perpustakaan, misalnya Guava 's Optionalatau FunctionalJava 's Option. Tetapi seperti banyak pola gaya fungsional, menggunakan Option di Java (bahkan 8) menghasilkan beberapa boilerplate, yang dapat Anda kurangi menggunakan bahasa JVM yang kurang verbose, misalnya Scala atau Xtend.

Jika Anda harus berurusan dengan API yang mungkin mengembalikan nol , Anda tidak bisa berbuat banyak di Jawa. Xtend dan Groovy memiliki operator Elvis ?: dan operator dereference null-safe ?. , tetapi perhatikan bahwa ini mengembalikan null jika ada referensi nol, jadi itu hanya "membela" penanganan null yang tepat.

thSoft
sumber
22
Memang, pola Opsi mengagumkan. Beberapa ekuivalen Jawa ada. Jambu biji mengandung versi terbatas dari yang disebut Opsional ini yang meninggalkan sebagian besar barang fungsional. Di Haskell, pola ini disebut Maybe.
Ben Hardy
9
Kelas opsional akan tersedia di Java 8
Pierre Henry
1
... dan belum (belum) memiliki peta atau flatMap: download.java.net/jdk8/docs/api/java/util/Optional.html
thSoft
3
Pola opsional tidak menyelesaikan apa pun; alih-alih satu objek yang berpotensi nol, sekarang Anda memiliki dua.
Boann
1
@Boann, jika digunakan dengan hati-hati, Anda menyelesaikan semua masalah NPE Anda. Jika tidak, maka saya kira ada masalah "penggunaan".
Louis F.
189

Hanya untuk situasi ini -

Tidak memeriksa apakah variabel nol sebelum memanggil metode sama dengan (contoh perbandingan string di bawah):

if ( foo.equals("bar") ) {
 // ...
}

akan menghasilkan NullPointerExceptionjika footidak ada.

Anda dapat menghindarinya jika membandingkan Stringseperti ini:

if ( "bar".equals(foo) ) {
 // ...
}
echox
sumber
42
Saya setuju - hanya dalam situasi itu. Saya tidak tahan programmer yang telah mengambil ini ke tingkat berikutnya yang tidak perlu dan menulis jika (null! = MyVar) ... hanya terlihat jelek bagi saya dan tidak ada gunanya!
Alex Worden
20
Ini adalah contoh khusus, mungkin yang paling banyak digunakan, dari praktik umum yang baik: jika Anda mengetahuinya, selalu lakukan <object that you know that is not null>.equals(<object that might be null>);. Ini berfungsi untuk metode lain selain equalsjika Anda mengetahui kontraknya dan metode tersebut dapat menangani nullparameter.
Stef
9
Ini adalah contoh pertama yang saya lihat tentang Kondisi Yoda yang benar-benar masuk akal
Erin Drummond
6
NullPointerExceptions dilempar karena suatu alasan. Mereka terlempar karena suatu benda adalah nol di mana seharusnya tidak. Ini adalah tugas pemrogram untuk MEMPERBAIKI ini, bukan MENYEMBUNKAN masalah.
Oliver Watkins
1
Try-Catch-DoNothing menyembunyikan masalah, ini adalah praktik yang valid untuk mengatasi kekurangan gula dalam bahasa.
echox
167

Dengan Java 8 hadir java.util.Optionalkelas baru yang bisa dipecahkan beberapa masalah. Paling tidak orang dapat mengatakan bahwa itu meningkatkan keterbacaan kode, dan dalam kasus API publik membuat kontrak API lebih jelas kepada pengembang klien.

Mereka bekerja seperti itu:

Objek opsional untuk tipe tertentu ( Fruit) dibuat sebagai tipe pengembalian metode. Itu bisa kosong atau mengandung Fruitobjek:

public static Optional<Fruit> find(String name, List<Fruit> fruits) {
   for (Fruit fruit : fruits) {
      if (fruit.getName().equals(name)) {
         return Optional.of(fruit);
      }
   }
   return Optional.empty();
}

Sekarang lihat kode ini di mana kita mencari daftar Fruit( fruits) untuk contoh Buah yang diberikan:

Optional<Fruit> found = find("lemon", fruits);
if (found.isPresent()) {
   Fruit fruit = found.get();
   String name = fruit.getName();
}

Anda dapat menggunakan map()operator untuk melakukan perhitungan pada - atau mengekstrak nilai dari - objek opsional. orElse()memungkinkan Anda memberikan fallback untuk nilai yang hilang.

String nameOrNull = find("lemon", fruits)
    .map(f -> f.getName())
    .orElse("empty-name");

Tentu saja, pemeriksaan untuk nilai nol / kosong masih diperlukan, tetapi setidaknya pengembang sadar bahwa nilai tersebut mungkin kosong dan risiko lupa untuk memeriksa terbatas.

Dalam API yang dibangun dari awal menggunakan Optionalsetiap kali nilai kembali mungkin kosong, dan mengembalikan objek polos hanya ketika itu tidak bisa null(konvensi), kode klien mungkin mengabaikan cek nol pada nilai-nilai pengembalian objek sederhana ...

Tentu saja Optionaljuga dapat digunakan sebagai argumen metode, mungkin cara yang lebih baik untuk menunjukkan argumen opsional dari 5 atau 10 metode kelebihan beban dalam beberapa kasus.

Optionalmenawarkan metode mudah lainnya, seperti orElseyang memungkinkan penggunaan nilai default, dan ifPresentyang berfungsi dengan ekspresi lambda .

Saya mengundang Anda untuk membaca artikel ini (sumber utama saya untuk menulis jawaban ini) di mana NullPointerException(dan secara umum null pointer) bermasalah serta solusi (parsial) yang dibawa Optionaldijelaskan dengan baik: Java Optional Objects .

Pierre Henry
sumber
11
Jambu Google memiliki implimensi opsional untuk Java 6+.
Bradley Gottfried
13
Ini sangat penting untuk menekankan bahwa menggunakan Opsional hanya dengan ifPresent () tidak tidak menambahkan banyak nilai di atas nol pengecekan normal. Nilai intinya adalah itu adalah monad yang dapat digunakan dalam rantai fungsi peta / flapMap, yang mencapai hasil yang mirip dengan operator Elvis di Groovy yang disebutkan di tempat lain. Meskipun tanpa penggunaan ini, saya menemukan sintaks orElse / orElseThrow juga sangat berguna.
Cornel Masson
Blog ini memiliki entri yang bagus di Opsional winterbe.com/posts/2015/03/15/avoid-null-checks-in-java
JohnC
4
Mengapa orang cenderung melakukan ini if(optional.isPresent()){ optional.get(); }sebagai gantioptional.ifPresent(o -> { ...})
Satyendra Kumar
1
Jadi, selain petunjuk kontraktual API, ini hanya tentang melayani programmer fungsional yang menikmati metode chaining tanpa henti.
naksir
125

Bergantung pada objek apa yang Anda periksa, Anda mungkin dapat menggunakan beberapa kelas di apache commons seperti: apache commons lang dan koleksi apache commons

Contoh:

String foo;
...
if( StringUtils.isBlank( foo ) ) {
   ///do something
}

atau (tergantung pada apa yang perlu Anda periksa):

String foo;
...
if( StringUtils.isEmpty( foo ) ) {
   ///do something
}

Kelas StringUtils hanya satu dari banyak; ada beberapa kelas bagus di commons yang melakukan manipulasi null aman.

Berikut ini contoh bagaimana Anda dapat menggunakan null vallidation di JAVA ketika Anda memasukkan pustaka apache (commons-lang-2.4.jar)

public DOCUMENT read(String xml, ValidationEventHandler validationEventHandler) {
    Validate.notNull(validationEventHandler,"ValidationHandler not Injected");
    return read(new StringReader(xml), true, validationEventHandler);
}

Dan jika Anda menggunakan Spring, Spring juga memiliki fungsi yang sama dalam paketnya, lihat pustaka (spring-2.4.6.jar)

Contoh tentang cara menggunakan classf statis ini dari musim semi (org.springframework.util.Assert)

Assert.notNull(validationEventHandler,"ValidationHandler not Injected");
javamonkey79
sumber
5
Anda juga dapat menggunakan versi yang lebih umum dari Apache Commons, cukup berguna di awal metode untuk memeriksa params yang saya temukan. Validate.notNull (objek, "objek tidak boleh null"); commons.apache.org/lang/apidocs/org/apache/commons/lang/…
monojohnny
@onojohnny apakah Validasi menggunakan pernyataan Tegas? saya meminta itu karena Assert dapat diaktifkan / dinonaktifkan pada JVM dan disarankan tidak digunakan dalam produksi.
Kurapika
Jangan berpikir begitu - saya percaya itu hanya melempar RuntimeException jika validasi gagal
monojohnny
96
  • Jika Anda menganggap suatu objek tidak boleh nol (atau itu bug) gunakan pernyataan.
  • Jika metode Anda tidak menerima params nol, ucapkan di javadoc dan gunakan penegasan.

Anda harus memeriksa objek! = Null hanya jika Anda ingin menangani case di mana objek tersebut mungkin nol ...

Ada proposal untuk menambahkan anotasi baru di Java7 untuk membantu dengan null / notnull params: http://tech.puredanger.com/java7/#jsr308

pgras
sumber
91

Saya penggemar kode "gagal cepat". Tanyakan pada diri sendiri - apakah Anda melakukan sesuatu yang bermanfaat dalam hal parameternya nol? Jika Anda tidak memiliki jawaban yang jelas untuk apa kode Anda harus dilakukan dalam kasus itu ... Ie itu tidak boleh nol di tempat pertama, maka abaikan saja dan biarkan NullPointerException dilempar. Kode panggilan akan masuk akal seperti NPE seperti halnya IllegalArgumentException, tetapi akan lebih mudah bagi pengembang untuk men-debug dan memahami apa yang salah jika NPE dilempar daripada kode Anda yang berusaha mengeksekusi kemungkinan tak terduga lainnya logika - yang pada akhirnya menyebabkan aplikasi gagal.

Alex Worden
sumber
2
lebih baik menggunakan pernyataan, yaitu Contract.notNull (abc, "abc harus non-null, apakah gagal memuat selama xyz?"); - ini adalah cara yang lebih ringkas daripada melakukan if (abc! = null) {throw RuntimeException baru ...}
ianpojman
76

Kerangka kerja koleksi Google menawarkan cara yang baik dan elegan untuk mencapai pemeriksaan nol.

Ada metode di kelas perpustakaan seperti ini:

static <T> T checkNotNull(T e) {
   if (e == null) {
      throw new NullPointerException();
   }
   return e;
}

Dan penggunaannya adalah import static:

...
void foo(int a, Person p) {
   if (checkNotNull(p).getAge() > a) {
      ...
   }
   else {
      ...
   }
}
...

Atau dalam contoh Anda:

checkNotNull(someobject).doCalc();
user2427
sumber
71
mmm, apa bedanya? p.getAge () akan melempar NPE yang sama dengan overhead yang lebih sedikit dan jejak stack yang lebih jelas. Apa yang saya lewatkan?
mysomic
15
Lebih baik melempar IllegalArgumentException ("e == null") dalam contoh Anda karena ini dengan jelas menunjukkan bahwa itu adalah pengecualian yang dimaksudkan oleh programmer (bersama dengan informasi yang cukup untuk benar-benar memungkinkan pengelola untuk mengidentifikasi masalah). NullPointerExceptions harus dicadangkan untuk JVM, karena kemudian jelas menunjukkan bahwa ini tidak disengaja (dan biasanya terjadi di suatu tempat yang sulit diidentifikasi)
Thorbjørn Ravn Andersen
6
Ini sekarang bagian dari Google Guava.
Steven Benitez
32
Baunya seperti teknik berlebihan bagi saya. Biarkan JVM melempar NPE dan jangan mengacaukan kode Anda dengan sampah ini.
Alex Worden
5
Saya suka dan membuka sebagian besar metode dan konstruktor dengan pemeriksaan eksplisit pada argumen; jika ada kesalahan, metode selalu gagal pada beberapa baris pertama dan saya tahu referensi yang menyinggung tanpa menemukan sesuatu sepertigetThing().getItsThing().getOtherThing().wowEncapsulationIsBroken().setLol("hi");
Cory Kendall
75

Terkadang, Anda memiliki metode yang beroperasi pada parameternya yang menentukan operasi simetris:

a.f(b); <-> b.f(a);

Jika Anda tahu b tidak pernah bisa menjadi nol, Anda bisa menukarnya. Ini paling berguna untuk yang sederajat: Alih-alih foo.equals("bar");melakukannya dengan lebih baik "bar".equals(foo);.

Johannes Schaub - litb
sumber
2
Tetapi kemudian Anda harus berasumsi equals(bisa berupa metode apa saja) akan menangani null dengan benar. Sebenarnya yang dilakukan adalah menyerahkan tanggung jawab kepada orang lain (atau metode lain).
Supericy
4
@Supericy Pada dasarnya ya, tapi equals(atau metode apa pun) harus memeriksa null. Atau nyatakan secara eksplisit bahwa tidak.
Angelo Fuchs
74

Daripada Null Object Pattern - yang memiliki kegunaannya - Anda dapat mempertimbangkan situasi di mana objek nol adalah bug.

Ketika pengecualian dilemparkan, periksa jejak tumpukan dan bekerja melalui bug.

Jim Nelson
sumber
17
Masalahnya adalah bahwa Anda biasanya kehilangan konteks karena NullPointerException tidak menunjukkan variabel WHICH adalah nol, dan Anda mungkin memiliki beberapa "." - operasi di telepon. Menggunakan "if (foo == null) melempar RuntimeException baru (" foo == null ")" memungkinkan Anda untuk menyatakan secara eksplisit APA salah, memberikan jejak stack lebih banyak nilai kepada mereka yang harus memperbaikinya.
Thorbjørn Ravn Andersen
1
Dengan Andersen - Saya akan senang sistem pengecualian Java untuk dapat memasukkan nama variabel yang sedang dikerjakan, sehingga NullPointerExceptions tidak hanya akan menunjukkan garis pengecualian terjadi, tetapi juga nama variabel. Ini seharusnya bekerja dengan baik di perangkat lunak yang tidak dikontrol.
fwielstra
Saya belum berhasil membuatnya bekerja, tapi ini dimaksudkan untuk menyelesaikan masalah itu.
MatrixFrog
Apa masalahnya? Cukup tangkap NPE pada tingkat yang sesuai di mana konteks yang cukup tersedia, buang informasi konteks dan kaji ulang pengecualian ... begitu mudah dengan Java.
user1050755
3
Saya memiliki seorang profesor yang mengajarkan metode chaining. Teorinya adalah bahwa Anda harus waspada terhadap rantai panggilan yang lebih panjang dari 2 metode. Saya tidak tahu apakah itu aturan yang sulit, tapi itu pasti menghilangkan sebagian besar masalah dengan jejak tumpukan NPE.
RustyTheBoyRobot
74

Null bukan 'masalah'. Ini adalah bagian integral dari set alat pemodelan lengkap . Perangkat lunak bertujuan untuk memodelkan kompleksitas dunia dan null menanggung bebannya. Null menunjukkan 'Tidak ada data' atau 'Tidak dikenal' di Jawa dan sejenisnya. Jadi pantas menggunakan nulls untuk tujuan ini. Saya tidak suka pola 'objek kosong'; Saya pikir itu muncul masalah ' siapa yang akan menjaga wali '.
Jika Anda bertanya kepada saya apa nama pacar saya, saya akan memberi tahu Anda bahwa saya tidak punya pacar. Dalam bahasa Java saya akan mengembalikan null. Alternatifnya adalah dengan melemparkan pengecualian yang berarti untuk menunjukkan beberapa masalah yang tidak dapat (atau tidak

  1. Untuk 'pertanyaan tidak dikenal', berikan 'jawaban tidak dikenal'. (Jadilah null-safe di mana ini benar dari sudut pandang bisnis) Memeriksa argumen untuk nol sekali di dalam suatu metode sebelum penggunaan membebaskan beberapa penelepon dari memeriksa mereka sebelum panggilan.

    public Photo getPhotoOfThePerson(Person person) {
        if (person == null)
            return null;
        // Grabbing some resources or intensive calculation
        // using person object anyhow.
    }

    Sebelumnya mengarah ke aliran logika normal untuk tidak mendapatkan foto pacar yang tidak ada dari perpustakaan foto saya.

    getPhotoOfThePerson(me.getGirlfriend())

    Dan itu cocok dengan Java API baru yang akan datang (menantikan)

    getPhotoByName(me.getGirlfriend()?.getName())

    Walaupun agak 'aliran bisnis normal' untuk tidak menemukan foto yang disimpan dalam DB untuk beberapa orang, saya biasa menggunakan pasangan seperti di bawah ini untuk beberapa kasus lain

    public static MyEnum parseMyEnum(String value); // throws IllegalArgumentException
    public static MyEnum parseMyEnumOrNull(String value);

    Dan jangan benci mengetik <alt> + <shift> + <j>(menghasilkan javadoc di Eclipse) dan menulis tiga kata tambahan untuk Anda API publik. Ini akan lebih dari cukup untuk semua kecuali mereka yang tidak membaca dokumentasi.

    /**
     * @return photo or null
     */

    atau

    /**
     * @return photo, never null
     */
  2. Ini adalah kasus yang agak teoretis dan dalam kebanyakan kasus Anda lebih suka java null safe API (dalam kasus ini akan dirilis dalam 10 tahun lagi), tetapi NullPointerExceptionmerupakan subkelas dari Exception. Jadi itu adalah bentuk Throwableyang menunjukkan kondisi yang mungkin ingin ditangkap oleh aplikasi yang masuk akal ( javadoc )! Untuk menggunakan sebagian besar keuntungan pertama dari pengecualian dan memisahkan kode penanganan kesalahan dari kode 'biasa' ( menurut pencipta Jawa ) adalah tepat, bagi saya, untuk menangkap NullPointerException.

    public Photo getGirlfriendPhoto() {
        try {
            return appContext.getPhotoDataSource().getPhotoByName(me.getGirlfriend().getName());
        } catch (NullPointerException e) {
            return null;
        }
    }

    Pertanyaan dapat muncul:

    Q. Bagaimana jika getPhotoDataSource()pengembalian nol?
    A. Terserah logika bisnis. Jika saya gagal menemukan album foto saya tidak akan menunjukkan foto. Bagaimana jika appContext tidak diinisialisasi? Logika bisnis metode ini cocok dengan ini. Jika logika yang sama harus lebih ketat maka melemparkan pengecualian itu adalah bagian dari logika bisnis dan pemeriksaan eksplisit untuk null harus digunakan (kasus 3). The cocok Java Null-aman API baru yang lebih baik di sini untuk menentukan selektif apa yang menyiratkan dan apa tidak berarti harus diinisialisasi menjadi gagal-cepat jika terjadi kesalahan programmer.

    P. Kode redundan dapat dieksekusi dan sumber daya yang tidak perlu bisa diambil.
    A. Itu bisa terjadi jika getPhotoByName()akan mencoba untuk membuka koneksi database, membuat PreparedStatementdan menggunakan nama orang sebagai parameter SQL pada akhirnya. Pendekatan untuk pertanyaan yang tidak diketahui memberikan jawaban yang tidak diketahui (kasus 1) bekerja di sini. Sebelum mengambil sumber daya, metode ini harus memeriksa parameter dan mengembalikan hasil 'tidak dikenal' jika diperlukan.

    Q. Pendekatan ini memiliki penalti kinerja karena pembukaan penutupan percobaan.
    A. Perangkat lunak harus mudah dipahami dan dimodifikasi terlebih dahulu. Hanya setelah ini, orang dapat berpikir tentang kinerja, dan hanya jika diperlukan! dan di mana dibutuhkan! ( sumber ), dan banyak lainnya).

    PS. Pendekatan ini akan masuk akal untuk digunakan karena kode penanganan kesalahan yang terpisah dari prinsip kode "biasa" masuk akal untuk digunakan di beberapa tempat. Perhatikan contoh berikut:

    public SomeValue calculateSomeValueUsingSophisticatedLogic(Predicate predicate) {
        try {
            Result1 result1 = performSomeCalculation(predicate);
            Result2 result2 = performSomeOtherCalculation(result1.getSomeProperty());
            Result3 result3 = performThirdCalculation(result2.getSomeProperty());
            Result4 result4 = performLastCalculation(result3.getSomeProperty());
            return result4.getSomeProperty();
        } catch (NullPointerException e) {
            return null;
        }
    }
    
    public SomeValue calculateSomeValueUsingSophisticatedLogic(Predicate predicate) {
        SomeValue result = null;
        if (predicate != null) {
            Result1 result1 = performSomeCalculation(predicate);
            if (result1 != null && result1.getSomeProperty() != null) {
                Result2 result2 = performSomeOtherCalculation(result1.getSomeProperty());
                if (result2 != null && result2.getSomeProperty() != null) {
                    Result3 result3 = performThirdCalculation(result2.getSomeProperty());
                    if (result3 != null && result3.getSomeProperty() != null) {
                        Result4 result4 = performLastCalculation(result3.getSomeProperty());
                        if (result4 != null) {
                            result = result4.getSomeProperty();
                        }
                    }
                }
            }
        }
        return result;
    }

    PPS. Bagi mereka yang cepat downvote (dan tidak terlalu cepat membaca dokumentasi) saya ingin mengatakan bahwa saya tidak pernah menangkap pengecualian null-pointer (NPE) dalam hidup saya. Tetapi kemungkinan ini sengaja dirancang oleh pencipta Jawa karena NPE adalah subkelas dari Exception. Kami memiliki preseden dalam sejarah Jawa saat ThreadDeathadalah Errortidak karena sebenarnya kesalahan aplikasi, tetapi semata-mata karena itu tidak dimaksudkan untuk ditangkap! Berapa banyak NPE yang cocok menjadi Errordaripada ThreadDeath! Tapi ternyata tidak.

  3. Periksa 'Tidak ada data' hanya jika logika bisnis menyiratkannya.

    public void updatePersonPhoneNumber(Long personId, String phoneNumber) {
        if (personId == null)
            return;
        DataSource dataSource = appContext.getStuffDataSource();
        Person person = dataSource.getPersonById(personId);
        if (person != null) {
            person.setPhoneNumber(phoneNumber);
            dataSource.updatePerson(person);
        } else {
            Person = new Person(personId);
            person.setPhoneNumber(phoneNumber);
            dataSource.insertPerson(person);
        }
    }

    dan

    public void updatePersonPhoneNumber(Long personId, String phoneNumber) {
        if (personId == null)
            return;
        DataSource dataSource = appContext.getStuffDataSource();
        Person person = dataSource.getPersonById(personId);
        if (person == null)
            throw new SomeReasonableUserException("What are you thinking about ???");
        person.setPhoneNumber(phoneNumber);
        dataSource.updatePerson(person);
    }

    Jika appContext atau dataSource tidak diinisialisasi runtime yang tidak ditangani, NullPointerException akan mematikan utas saat ini dan akan diproses oleh Thread.defaultUncaughtExceptionHandler (untuk Anda menentukan dan menggunakan logger favorit Anda atau mekanisme pemberitahuan lainnya). Jika tidak disetel, ThreadGroup # uncaughtException akan mencetak stacktrace ke kesalahan sistem. Seseorang harus memonitor log kesalahan aplikasi dan membuka masalah Jira untuk setiap pengecualian yang tidak tertangani yang sebenarnya adalah kesalahan aplikasi. Programmer harus memperbaiki bug di suatu tempat dalam hal inisialisasi.

Mykhaylo Adamovych
sumber
6
Menangkap NullPointerExceptiondan kembali nullmengerikan untuk debug. Lagi pula, Anda berakhir dengan NPE, dan sangat sulit untuk mengetahui apa yang semula nol.
artbristol
23
Saya akan menurunkan suara jika saya memiliki reputasi. Tidak hanya null tidak perlu, itu adalah lubang di sistem tipe. Menetapkan Tree ke Daftar adalah kesalahan tipe karena pohon bukan nilai dari daftar Tipe; oleh logika yang sama, menetapkan null harus menjadi kesalahan tipe karena null bukan nilai dari objek tipe, atau tipe berguna apa pun dalam hal ini. Bahkan orang yang menemukan null menganggapnya sebagai "kesalahan miliaran dolar". Gagasan "nilai yang mungkin merupakan nilai tipe T ATAU tidak ada" adalah tipenya sendiri, dan harus dinyatakan seperti itu (mis. Mungkin <T> atau Opsional <T>).
Doval
2
Pada "Mungkin <T> atau Opsional <T>" Anda masih perlu menulis kode seperti if (maybeNull.hasValue()) {...}apa bedanya if (maybeNull != null)) {...}?
Mykhaylo Adamovych
1
Pada "menangkap NullPointerException dan mengembalikan null mengerikan untuk di-debug. Lagi pula, Anda berakhir dengan NPE, dan sangat sulit untuk mengetahui apa yang semula nol". Saya sepenuhnya setuju! Dalam kasus tersebut Anda harus menulis selusin pernyataan 'jika' atau melempar NPE jika logika bisnis menyiratkan data di tempat, atau menggunakan operator null-safe dari Jawa baru. Tetapi ada beberapa kasus ketika saya tidak peduli tentang langkah apa yang memberi saya nol. Misalnya menghitung beberapa nilai untuk pengguna sebelum ditampilkan di layar saat Anda mengharapkan data bisa hilang.
Mykhaylo Adamovych
3
@MykhayloAdamovych: Manfaat Maybe<T>atau Optional<T>tidak dalam kasus di mana Anda Tmungkin nol, tetapi dalam kasus di mana seharusnya tidak boleh nol. Jika Anda memiliki jenis yang secara eksplisit berarti "nilai ini mungkin nol - gunakan dengan hati-hati", dan Anda menggunakan dan mengembalikan jenis seperti itu secara konsisten, maka setiap kali Anda melihat polos lama Tdalam kode Anda, Anda dapat menganggap itu tidak pernah nol. (Tentu saja, ini akan jauh lebih berguna jika diberlakukan oleh kompiler.)
cHao
71

Java 7 memiliki java.util.Objectskelas utilitas baru di mana ada requireNonNull()metode. Yang dilakukan adalah membuang NullPointerExceptionargumennya jika null, tetapi sedikit membersihkan kode. Contoh:

Objects.requireNonNull(someObject);
someObject.doCalc();

Metode ini paling berguna untuk memeriksa sesaat sebelum penugasan dalam konstruktor, di mana setiap penggunaannya dapat menyimpan tiga baris kode:

Parent(Child child) {
   if (child == null) {
      throw new NullPointerException("child");
   }
   this.child = child;
}

menjadi

Parent(Child child) {
   this.child = Objects.requireNonNull(child, "child");
}
Raedwald
sumber
9
Sebenarnya, contoh Anda merupakan kode mengasapi: baris pertama berlebihan karena NPE akan dibuang di baris kedua. ;-)
user1050755
1
Benar. Contoh yang lebih baik adalah jika baris kedua adalah doCalc(someObject).
Stuart Marks
Tergantung. Jika Anda adalah penulis doCalc (), saya sarankan memasukkan cek ke dalam tubuh metode itu (jika mungkin). Dan kemudian Anda kemungkinan besar akan memanggil someObject.someMethod () di mana lagi tidak perlu memeriksa nol. :-)
user1050755
Nah, jika Anda bukan pembuatnya doCalc(), dan itu tidak langsung melempar NPE ketika diberi nol, Anda harus memeriksa nol dan melemparkan NPE sendiri. Itu untuk apa Objects.requireNonNull().
Stuart Marks
7
Ini bukan hanya kode mengasapi. Lebih baik memeriksa di depan daripada setengah jalan melalui metode yang menyebabkan efek samping atau menggunakan waktu / ruang.
Rob Grant
51

Pada akhirnya, satu-satunya cara untuk menyelesaikan masalah ini adalah dengan menggunakan bahasa pemrograman yang berbeda:

  • Di Objective-C, Anda dapat melakukan hal yang sama dengan memanggil metode nil, dan sama sekali tidak akan terjadi apa-apa. Ini membuat sebagian besar pemeriksaan nol tidak perlu, tetapi dapat membuat kesalahan lebih sulit untuk didiagnosis.
  • Di Nice , bahasa yang diturunkan dari Jawa, ada dua versi dari semua jenis: versi yang berpotensi null dan versi yang bukan nol. Anda hanya dapat memanggil metode pada tipe yang bukan nol. Tipe yang berpotensi null dapat dikonversi menjadi tipe yang tidak nol melalui pemeriksaan eksplisit untuk null. Ini membuatnya lebih mudah untuk mengetahui di mana pemeriksaan nol diperlukan dan di mana tidak.
Michael Borgwardt
sumber
Woah ... Jawaban yang paling benar dan jawabannya menurun, di mana keadilannya? Dalam Java null selalu merupakan nilai yang valid. Ini adalah akibat wajar untuk Semuanya adalah Obyek - Null adalah Segalanya (tentu saja kita mengabaikan primitif di sini tetapi Anda mendapatkan ide). Secara pribadi saya mendukung pendekatan yang diambil oleh Nice, meskipun kita bisa membuatnya sehingga metode dapat dipanggil pada tipe nullable dan mempromosikan NPE ke pengecekan pengecualian. Ini harus dilakukan melalui saklar kompiler karena akan memecah semua kode yang ada :(
CurtainDog
4
Saya tidak terbiasa dengan Nice, tetapi Kotlin mengimplementasikan ide yang sama, memiliki tipe nullable dan not-null ke dalam sistem tipe bahasa. Jauh lebih ringkas daripada Opsional atau pola Objek nol.
mtsahakis
38

"Masalah" yang biasa terjadi di Jawa.

Pertama, pikiran saya tentang ini:

Saya menganggap itu buruk untuk "makan" sesuatu ketika NULL disahkan di mana NULL bukan nilai yang valid. Jika Anda tidak keluar dari metode dengan semacam kesalahan maka itu berarti tidak ada yang salah dalam metode Anda yang tidak benar. Maka Anda mungkin mengembalikan nol dalam kasus ini, dan dalam metode penerimaan Anda kembali memeriksa nol, dan itu tidak pernah berakhir, dan Anda berakhir dengan "jika! = Null", dll.

Jadi, IMHO, null harus merupakan kesalahan kritis yang mencegah eksekusi lebih lanjut (yaitu, di mana null bukan nilai yang valid).

Cara saya mengatasi masalah ini adalah ini:

Pertama, saya mengikuti konvensi ini:

  1. Semua metode publik / API selalu memeriksa argumennya untuk null
  2. Semua metode pribadi tidak memeriksa nol karena mereka adalah metode yang dikendalikan (biarkan mati dengan pengecualian nullpointer jika tidak ditangani di atas)
  3. Satu-satunya metode lain yang tidak memeriksa null adalah metode utilitas. Mereka bersifat publik, tetapi jika Anda memanggilnya karena suatu alasan, Anda tahu parameter apa yang Anda lewati. Ini seperti mencoba merebus air dalam ketel tanpa menyediakan air ...

Dan akhirnya, dalam kode, baris pertama dari metode publik berbunyi seperti ini:

ValidationUtils.getNullValidator().addParam(plans, "plans").addParam(persons, "persons").validate();

Perhatikan bahwa addParam () mengembalikan diri, sehingga Anda dapat menambahkan lebih banyak parameter untuk memeriksa.

Metode validate()akan membuang diperiksa ValidationExceptionjika salah satu parameter adalah nol (dicentang atau tidak dicentang lebih merupakan masalah desain / rasa, tapi saya ValidationExceptiondiperiksa).

void validate() throws ValidationException;

Pesan akan berisi teks berikut jika, misalnya, "paket" adalah nol:

" Nilai argumen ilegal null ditemukan untuk parameter [paket] "

Seperti yang Anda lihat, nilai kedua dalam metode addParam () (string) diperlukan untuk pesan pengguna, karena Anda tidak dapat dengan mudah mendeteksi nama variabel yang diteruskan, bahkan dengan refleksi (toh bukan subjek dari posting ini ...).

Dan ya, kita tahu bahwa di luar garis ini kita tidak akan lagi menemukan nilai nol sehingga kita hanya dengan aman memanggil metode pada objek tersebut.

Dengan cara ini, kodenya bersih, mudah dirawat dan dapat dibaca.

Oleg
sumber
3
Benar. Aplikasi yang hanya melempar kesalahan dan kerusakan berkualitas lebih tinggi karena tidak ada keraguan ketika mereka tidak berfungsi. Aplikasi yang menelan kesalahan paling baik menurunkan anggun tetapi biasanya tidak bekerja dengan cara yang sulit untuk diperhatikan dan tidak diperbaiki. Dan ketika masalah diketahui, mereka jauh lebih sulit untuk di-debug.
Paul Jackson
35

Mengajukan pertanyaan itu menunjukkan bahwa Anda mungkin tertarik dengan strategi penanganan kesalahan. Arsitek tim Anda harus memutuskan cara mengatasi kesalahan. Ada beberapa cara untuk melakukan ini:

  1. memungkinkan Pengecualian untuk beriak - tangkap di 'loop utama' atau dalam beberapa rutinitas pengelolaan lainnya.

    • memeriksa kondisi kesalahan dan menanganinya dengan tepat

Tentu saja lihat Pemrograman Berorientasi Aspek, juga - mereka memiliki cara rapi untuk memasukkan if( o == null ) handleNull()ke dalam bytecode Anda.

xtofl
sumber
35

Selain menggunakan assertAnda dapat menggunakan yang berikut:

if (someobject == null) {
    // Handle null here then move on.
}

Ini sedikit lebih baik daripada:

if (someobject != null) {
    .....
    .....



    .....
}
fastcodejava
sumber
2
Mh, kenapa begitu? Tolong jangan merasa defensif, saya hanya ingin belajar lebih banyak tentang Java :)
Matthias Meid
9
@Mudu Sebagai aturan umum, saya lebih suka ekspresi dalam pernyataan if untuk menjadi pernyataan yang lebih "positif", daripada yang "negatif". Jadi jika saya melihat if (!something) { x(); } else { y(); }saya akan cenderung untuk refactor sebagai if (something) { y(); } else { x(); }(meskipun orang dapat berpendapat bahwa itu != nulladalah pilihan yang lebih positif ...) Tetapi yang lebih penting, bagian penting dari kode tidak dibungkus di dalam {}dan Anda memiliki satu lekukan tingkat kurang untuk sebagian besar metode. Saya tidak tahu apakah itu alasan fastcodejava tapi itu akan menjadi milik saya.
MatrixFrog
1
Inilah yang cenderung saya lakukan juga .. Menjaga kode tetap bersih dalam pendapat saya.
Koray Tugay
34

Hanya saja jangan pernah menggunakan null. Jangan izinkan.

Di kelas saya, sebagian besar bidang dan variabel lokal memiliki nilai default non-nol, dan saya menambahkan pernyataan kontrak (selalu-on menegaskan) di mana pun dalam kode untuk memastikan ini ditegakkan (karena lebih ringkas, dan lebih ekspresif daripada membiarkannya muncul sebagai NPE dan kemudian harus menyelesaikan nomor baris, dll.).

Begitu saya mengadopsi praktik ini, saya perhatikan bahwa masalah-masalah itu sepertinya memperbaiki diri. Anda akan menangkap banyak hal lebih awal dalam proses pengembangan hanya secara tidak sengaja dan menyadari bahwa Anda memiliki titik lemah .. dan yang lebih penting .. ini membantu merangkum keprihatinan modul yang berbeda, modul yang berbeda dapat saling 'percaya', dan tidak lagi membuang sampah sembarangan kode dengan if = null elsekonstruk!

Ini adalah pemrograman defensif dan menghasilkan kode yang lebih bersih dalam jangka panjang. Selalu bersihkan data, misalnya di sini dengan menegakkan standar yang kaku, dan masalahnya hilang.

class C {
    private final MyType mustBeSet;
    public C(MyType mything) {
       mustBeSet=Contract.notNull(mything);
    }
   private String name = "<unknown>";
   public void setName(String s) {
      name = Contract.notNull(s);
   }
}


class Contract {
    public static <T> T notNull(T t) { if (t == null) { throw new ContractException("argument must be non-null"); return t; }
}

Kontrak seperti tes unit mini yang selalu berjalan, bahkan dalam produksi, dan ketika semuanya gagal, Anda tahu mengapa, daripada NPE acak Anda harus entah bagaimana mencari tahu.

iangreen
sumber
mengapa ini akan diturunkan? dalam pengalaman saya, ini jauh lebih unggul dari pendekatan lain, akan senang tahu mengapa tidak
ianpojman
Saya setuju, pendekatan ini mencegah masalah yang terkait dengan nulls, daripada memperbaikinya dengan speckling code null checks di mana-mana.
ChrisBlom
7
Masalah dengan pendekatan ini adalah bahwa jika nama tidak pernah diset, itu memiliki nilai "<unknown>", yang berperilaku seperti nilai yang ditetapkan. Sekarang katakanlah saya perlu memeriksa apakah nama tidak pernah disetel (tidak dikenal), saya harus melakukan perbandingan string terhadap nilai khusus "<unknown>".
Steve Kuo
1
Poin bagus, Steve. Apa yang sering saya lakukan adalah memiliki nilai tersebut sebagai konstanta, mis. String public final static UNSET = "__ unset" ... private String field = UNSET ... lalu private boolean isSet () {return UNSET.equals (field); }
ianpojman
IMHO, Ini adalah implementasi Pola Obyek Null dengan implementasi Opsional (Kontrak) sendiri. Bagaimana perilakunya di kelas kelas kegigihan? Saya tidak melihat itu berlaku dalam kasus itu.
Kurapika
31

Guava, pustaka inti yang sangat berguna oleh Google, memiliki API yang bagus dan berguna untuk menghindari nulls. Saya menemukan UsingAndAvoidingNullExplained sangat membantu.

Seperti yang dijelaskan di wiki:

Optional<T>adalah cara mengganti referensi T yang dapat dibatalkan dengan nilai yang bukan nol. Opsional dapat mengandung referensi T non-null (dalam hal ini kami mengatakan referensi tersebut "hadir"), atau mungkin tidak mengandung apa pun (dalam hal ini kami mengatakan referensi tersebut "tidak ada"). Tidak pernah dikatakan "mengandung null."

Pemakaian:

Optional<Integer> possible = Optional.of(5);
possible.isPresent(); // returns true
possible.get(); // returns 5
Murat
sumber
@CodyGuldner Benar, Cody. Saya memberikan kutipan yang relevan dari tautan untuk memberikan lebih banyak konteks.
Murat Derya Özen
25

Ini adalah masalah yang sangat umum untuk setiap pengembang Java. Jadi ada dukungan resmi di Java 8 untuk mengatasi masalah ini tanpa kode yang berantakan.

Java 8 telah diperkenalkan java.util.Optional<T>. Ini adalah wadah yang mungkin atau mungkin tidak memiliki nilai bukan nol. Java 8 telah memberikan cara yang lebih aman untuk menangani objek yang nilainya mungkin nol dalam beberapa kasus. Ini terinspirasi dari ide-ide Haskell dan Scala .

Singkatnya, kelas opsional mencakup metode untuk secara eksplisit menangani kasus-kasus di mana nilai hadir atau tidak ada. Namun, keuntungan dibandingkan dengan referensi nol adalah bahwa kelas <T> opsional memaksa Anda untuk memikirkan kasus ketika nilainya tidak ada. Sebagai konsekuensinya, Anda dapat mencegah pengecualian null pointer yang tidak diinginkan.

Dalam contoh di atas kami memiliki pabrik layanan rumah yang mengembalikan pegangan ke beberapa peralatan yang tersedia di rumah. Tetapi layanan ini mungkin atau mungkin tidak tersedia / fungsional; itu berarti dapat menghasilkan NullPointerException. Alih-alih menambahkan ifkondisi nol sebelum menggunakan layanan apa pun, mari membungkusnya dengan Opsional <Layanan>.

Bungkus DENGAN OPSI <T>

Mari pertimbangkan metode untuk mendapatkan referensi layanan dari pabrik. Alih-alih mengembalikan referensi layanan, bungkus dengan Opsional. Ini memberi tahu pengguna API bahwa layanan yang dikembalikan mungkin atau mungkin tidak tersedia / berfungsi, gunakan secara defensif

public Optional<Service> getRefrigertorControl() {
      Service s = new  RefrigeratorService();
       //...
      return Optional.ofNullable(s);
   }

Seperti yang Anda lihat Optional.ofNullable()memberikan cara mudah untuk mendapatkan referensi dibungkus. Ada cara lain untuk mendapatkan referensi Opsional, baik Optional.empty()& Optional.of(). Satu untuk mengembalikan objek kosong alih-alih mengembalikan nol dan yang lainnya untuk membungkus objek yang tidak dapat dibatalkan, masing-masing.

BEGITU BAGAIMANA ITU MEMBANTU UNTUK MENGHINDARI NULL CHECK?

Setelah Anda membungkus objek referensi, Opsional menyediakan banyak metode yang berguna untuk memanggil metode pada referensi yang dibungkus tanpa NPE.

Optional ref = homeServices.getRefrigertorControl();
ref.ifPresent(HomeServices::switchItOn);

Optional.ifPresent memanggil Konsumen yang diberikan dengan referensi jika itu adalah nilai non-nol. Kalau tidak, ia tidak melakukan apa-apa.

@FunctionalInterface
public interface Consumer<T>

Merupakan operasi yang menerima argumen input tunggal dan tidak mengembalikan hasil. Tidak seperti kebanyakan antarmuka fungsional lainnya, Konsumen diharapkan beroperasi melalui efek samping. Sangat bersih dan mudah dimengerti. Dalam contoh kode di atas, HomeService.switchOn(Service)dipanggil jika referensi penahanan opsional adalah bukan nol.

Kami sangat sering menggunakan operator ternary untuk memeriksa kondisi nol dan mengembalikan nilai alternatif atau nilai default. Opsional menyediakan cara lain untuk menangani kondisi yang sama tanpa memeriksa nol. Optional.orElse (defaultObj) mengembalikan defaultObj jika Opsional memiliki nilai nol. Mari kita gunakan ini dalam kode sampel kami:

public static Optional<HomeServices> get() {
    service = Optional.of(service.orElse(new HomeServices()));
    return service;
}

Sekarang HomeServices.get () melakukan hal yang sama, tetapi dengan cara yang lebih baik. Ia memeriksa apakah layanan sudah diinisialisasi belum. Jika kemudian mengembalikan yang sama atau membuat layanan baru. Opsional <T> .orElse (T) membantu mengembalikan nilai default.

Akhirnya, inilah NPE kami dan juga kode bebas-cek nol:

import java.util.Optional;
public class HomeServices {
    private static final int NOW = 0;
    private static Optional<HomeServices> service;

public static Optional<HomeServices> get() {
    service = Optional.of(service.orElse(new HomeServices()));
    return service;
}

public Optional<Service> getRefrigertorControl() {
    Service s = new  RefrigeratorService();
    //...
    return Optional.ofNullable(s);
}

public static void main(String[] args) {
    /* Get Home Services handle */
    Optional<HomeServices> homeServices = HomeServices.get();
    if(homeServices != null) {
        Optional<Service> refrigertorControl = homeServices.get().getRefrigertorControl();
        refrigertorControl.ifPresent(HomeServices::switchItOn);
    }
}

public static void switchItOn(Service s){
         //...
    }
}

Posting lengkapnya adalah NPE dan juga kode bebas-cek Null… Benarkah? .

Yogesh Devatraj
sumber
Ada cek nol di kode di atas - if(homeServices != null) {yang dapat diubah menjadi homeServices.ifPresent(h -> //action);
KrishPrabakar
23

Saya suka artikel dari Nat Pryce. Inilah tautannya:

Dalam artikel tersebut ada juga tautan ke repositori Git untuk Java Maybe Type yang menurut saya menarik, tetapi saya tidak berpikir itu sendiri dapat mengurangi mengasapi kode pemeriksaan. Setelah melakukan riset di Internet, saya pikir ! = Kode nol mengasapi dapat dikurangi terutama dengan desain yang cermat.

Mr Palo
sumber
Michael Feathers telah menulis teks pendek dan menarik tentang pendekatan seperti yang Anda sebutkan: manuelp.newsblur.com/site/424
ivan.aguirre
21

Saya sudah mencoba NullObjectPatterntetapi bagi saya tidak selalu cara terbaik untuk pergi. Terkadang ada "tidak ada tindakan" yang tidak sesuai.

NullPointerExceptionadalah pengecualian Runtime yang berarti kesalahan pengembang dan dengan pengalaman yang cukup itu memberi tahu Anda persis di mana kesalahannya.

Sekarang untuk jawabannya:

Cobalah untuk membuat semua atribut Anda dan aksesornya se-privat mungkin atau hindari untuk mengungkapkannya kepada klien sama sekali. Anda dapat memiliki nilai argumen di konstruktor tentu saja, tetapi dengan mengurangi ruang lingkup Anda tidak membiarkan kelas klien melewati nilai yang tidak valid. Jika Anda perlu mengubah nilai, Anda selalu dapat membuat yang baru object. Anda memeriksa nilai-nilai di konstruktor hanya sekali dan di seluruh metode Anda dapat hampir yakin bahwa nilai-nilai tidak nol.

Tentu saja, pengalaman adalah cara yang lebih baik untuk memahami dan menerapkan saran ini.

Byte!

OscarRyz
sumber
19

Mungkin alternatif terbaik untuk Java 8 atau yang lebih baru adalah menggunakan Optionalkelas.

Optional stringToUse = Optional.of("optional is there");
stringToUse.ifPresent(System.out::println);

Ini sangat berguna untuk rantai panjang nilai null yang mungkin. Contoh:

Optional<Integer> i = Optional.ofNullable(wsObject.getFoo())
    .map(f -> f.getBar())
    .map(b -> b.getBaz())
    .map(b -> b.getInt());

Contoh tentang cara melempar pengecualian pada null:

Optional optionalCarNull = Optional.ofNullable(someNull);
optionalCarNull.orElseThrow(IllegalStateException::new);

Java 7 memperkenalkan Objects.requireNonNullmetode yang bisa berguna ketika sesuatu harus diperiksa untuk tidak null. Contoh:

String lowerVal = Objects.requireNonNull(someVar, "input cannot be null or empty").toLowerCase();
Raghu K Nair
sumber
17

Bolehkah saya menjawabnya secara lebih umum!

Kami biasanya menghadapi masalah ini ketika metode mendapatkan parameter dengan cara yang tidak kami harapkan (panggilan metode buruk adalah kesalahan programmer). Misalnya: Anda berharap mendapatkan objek, sebaliknya Anda mendapatkan nol. Anda berharap mendapatkan string dengan setidaknya satu karakter, alih-alih Anda mendapatkan String kosong ...

Jadi tidak ada perbedaan antara:

if(object == null){
   //you called my method badly!

}

atau

if(str.length() == 0){
   //you called my method badly again!
}

Mereka berdua ingin memastikan bahwa kami menerima parameter yang valid, sebelum kami melakukan fungsi lain.

Seperti disebutkan dalam beberapa jawaban lain, untuk menghindari masalah di atas Anda dapat mengikuti Desain dengan pola kontrak . Silakan lihat http://en.wikipedia.org/wiki/Design_by_contract .

Untuk menerapkan pola ini di java, Anda dapat menggunakan penjelasan inti java seperti javax.annotation.NotNull atau menggunakan perpustakaan yang lebih canggih seperti Hibernate Validator .

Hanya sampel:

getCustomerAccounts(@NotEmpty String customerId,@Size(min = 1) String accountType)

Sekarang Anda dapat dengan aman mengembangkan fungsi inti dari metode Anda tanpa perlu memeriksa parameter input, mereka menjaga metode Anda dari parameter yang tidak terduga.

Anda dapat melangkah lebih jauh dan memastikan bahwa hanya pojo yang valid yang dapat dibuat di aplikasi Anda. (contoh dari situs validator hibernasi)

public class Car {

   @NotNull
   private String manufacturer;

   @NotNull
   @Size(min = 2, max = 14)
   private String licensePlate;

   @Min(2)
   private int seatCount;

   // ...
}
Alireza Fattahi
sumber
javaxmenurut definisi, bukan "Java inti".
Tomas
17

Saya sangat mengabaikan jawaban yang menyarankan menggunakan objek nol dalam setiap situasi. Pola ini dapat memutus kontrak dan mengubur masalah lebih dalam dan lebih dalam daripada menyelesaikannya, tidak menyebutkan bahwa penggunaan yang tidak tepat akan membuat tumpukan kode boilerplate lain yang akan membutuhkan pemeliharaan di masa depan.

Pada kenyataannya jika sesuatu yang dikembalikan dari suatu metode bisa menjadi nol dan kode panggilan harus membuat keputusan tentang itu, harus ada panggilan sebelumnya yang memastikan keadaan.

Juga perlu diingat, bahwa pola objek nol akan menjadi haus memori jika digunakan tanpa perawatan. Untuk ini - instance dari NullObject harus dibagikan di antara pemilik, dan tidak menjadi instance unigue untuk masing-masing.

Juga saya tidak akan merekomendasikan menggunakan pola ini di mana tipe dimaksudkan untuk representasi tipe primitif - seperti entitas matematika, yang tidak skalar: vektor, matriks, bilangan kompleks, dan objek POD (Data Lama Biasa), yang dimaksudkan untuk menahan keadaan dalam bentuk tipe bawaan Java. Dalam kasus terakhir Anda akan memanggil metode pengambil dengan hasil yang sewenang-wenang. Misalnya apa yang harus dikembalikan oleh metode NullPerson.getName ()?

Ada baiknya mempertimbangkan kasus-kasus seperti itu untuk menghindari hasil yang absurd.

luke1985
sumber
Solusi dengan "hasBackground ()" memiliki satu kelemahan - tidak aman untuk thread. Jika Anda perlu memanggil dua metode, bukan satu, Anda perlu menyinkronkan seluruh urutan dalam lingkungan multi-utas.
pkalinow
@ pkalinow Anda membuat contoh yang dibuat hanya untuk menunjukkan bahwa solusi ini memiliki kekurangan. Jika kode tidak dimaksudkan untuk berjalan di aplikasi multithreaded maka tidak ada kekurangan. Saya bisa memberi Anda 90% dari kode Anda yang tidak aman. Kami tidak berbicara di sini tentang aspek kode ini, kami berbicara tentang pola desain. Dan multithreading adalah topik tersendiri.
luke1985
Tentu saja dalam aplikasi single-thread itu tidak masalah. Saya sudah memberikan komentar itu karena terkadang itu adalah masalah.
pkalinow
@ pkalinow Jika Anda mempelajari topik ini lebih dekat, Anda akan mengetahui bahwa pola desain Obyek Null tidak akan memperbaiki masalah multithreading. Jadi itu tidak relevan. Dan sejujurnya, saya telah menemukan tempat-tempat di mana pola ini cocok dengan baik, jadi jawaban awal saya agak salah, sebenarnya.
luke1985
16
  1. Jangan pernah menginisialisasi variabel ke nol.
  2. Jika (1) tidak memungkinkan, inisialisasi semua koleksi dan array ke koleksi kosong / array.

Melakukan ini dalam kode Anda sendiri dan Anda dapat menghindari! = Cek kosong.

Sebagian besar waktu, cek nol tampaknya menjaga loop atas koleksi atau array, jadi inisialisasi saja kosong, Anda tidak akan memerlukan pemeriksaan nol.

// Bad
ArrayList<String> lemmings;
String[] names;

void checkLemmings() {
    if (lemmings != null) for(lemming: lemmings) {
        // do something
    }
}



// Good
ArrayList<String> lemmings = new ArrayList<String>();
String[] names = {};

void checkLemmings() {
    for(lemming: lemmings) {
        // do something
    }
}

Ada overhead kecil dalam hal ini, tetapi itu layak untuk kode yang lebih bersih dan lebih sedikit NullPointerExceptions.

Stuart Axon
sumber
3
+1 Saya setuju dengan ini. Anda tidak boleh mengembalikan setengah objek yang diinisialisasi. Kode yang terkait dengan Jaxb dan kode kacang tidak penting untuk ini. Ini praktik buruk. Semua koleksi harus diinisialisasi, dan semua objek harus ada dengan (idealnya) tanpa referensi nol. Pertimbangkan objek yang memiliki koleksi di dalamnya. Memeriksa bahwa objek tersebut bukan nol, bahwa koleksinya bukan nol dan bahwa koleksinya tidak mengandung objek nol adalah tidak masuk akal dan bodoh.
ggb667
15

Ini adalah kesalahan paling umum terjadi untuk sebagian besar pengembang.

Kami memiliki sejumlah cara untuk menangani ini.

Pendekatan 1:

org.apache.commons.lang.Validate //using apache framework

notNull (Objek objek, pesan String)

Pendekatan 2:

if(someObject!=null){ // simply checking against null
}

Pendekatan 3:

@isNull @Nullable  // using annotation based validation

Pendekatan 4:

// by writing static method and calling it across whereever we needed to check the validation

static <T> T isNull(someObject e){  
   if(e == null){
      throw new NullPointerException();
   }
   return e;
}
Sireesh Yarlagadda
sumber
Iklan. 4. Ini tidak terlalu berguna - ketika Anda memeriksa apakah pointer adalah null, Anda mungkin ingin memanggil metode di atasnya. Memanggil metode dengan null memberi Anda perilaku yang sama - NullPointerException.
pkalinow
11
public static <T> T ifNull(T toCheck, T ifNull) {
    if (toCheck == null) {
           return ifNull;
    }
    return toCheck;
}
tltester
sumber
2
Apa yang salah dengan metode ini, saya pikir @tltester hanya ingin memberikan nilai default jika itu nol, yang masuk akal.
Sawyer
1
Ada metode seperti di Apache commons-lang: ObjectUtils.defaultIfNull(). Ada satu yang lebih umum ObjectUtils.firstNonNull()firstNonNull(bestChoice, secondBest, thirdBest, fallBack);
:,