Izbjegavajte provjeru nule izjave u Javi

1. Pregled

Općenito, null varijablama, referencama i kolekcijama teško je rukovati u Java kodu. Ne samo da ih je teško prepoznati, već su i složeni za rješavanje.

Zapravo, bilo kakva greška u rješavanju null ne može se identificirati u vrijeme sastavljanja i rezultira a NullPointerException za vrijeme izvođenja.

U ovom ćemo uputstvu pogledati potrebu za provjerom null na Javi i razne alternative koje nam pomažu izbjeći null provjerava naš kod.

2. Što je NullPointerException?

Prema Javadocu za NullPointerException, baca se kada aplikacija pokušava koristiti null u slučaju kada je potreban objekt, kao što su:

  • Pozivanje metode instance a null objekt
  • Pristupanje ili izmjena polja a null objekt
  • Uzimajući dužinu null kao da je riječ o nizu
  • Pristup ili izmjena utora null kao da je riječ o nizu
  • Bacanje null kao da je a Bacljivo vrijednost

Pogledajmo brzo nekoliko primjera Java koda koji uzrokuju ovu iznimku:

javna praznina doSomething () {Rezultat niza = doSomethingElse (); if (result.equalsIgnoreCase ("Success")) // uspjeh}} private String doSomethingElse () {return null; }

Ovdje, pokušali smo pozvati poziv metode za null referenca. To bi rezultiralo a NullPointerException.

Sljedeći je čest primjer ako pokušamo pristupiti a null niz:

javna statička void glavna (String [] args) {findMax (null); } privatna statička praznina findMax (int [] arr) {int max = arr [0]; // provjera ostalih elemenata u petlji}

To uzrokuje a NullPointerException u 6. redu.

Dakle, pristup bilo kojem polju, metodi ili indeksu a null objekt uzrokuje a NullPointerException, kao što se može vidjeti iz gornjih primjera.

Uobičajeni način izbjegavanja NullPointerException je provjeriti null:

javna praznina doSomething () {Rezultat niza = doSomethingElse (); if (result! = null && result.equalsIgnoreCase ("Success")) {// uspjeh} else // neuspjeh} private String doSomethingElse () {return null; }

U stvarnom svijetu programeri teško mogu identificirati koji objekti mogu biti null. Agresivno sigurna strategija može biti provjera null za svaki objekt. To, međutim, uzrokuje puno suvišnih null provjerava i čini naš kôd manje čitljivim.

U sljedećih nekoliko odjeljaka proći ćemo kroz neke alternative u Javi koje izbjegavaju takvu suvišnost.

3. Rukovanje null Kroz API ugovor

Kao što je raspravljeno u prošlom odjeljku, pristup metodama ili varijablama null predmeta uzrokuje a NullPointerException. Također smo razgovarali o tome da stavljanje a null provjera objekta prije pristupanja eliminira mogućnost NullPointerException.

Međutim, često postoje API-ji koji se mogu nositi s tim null vrijednosti. Na primjer:

javni void ispis (parametar objekta) {System.out.println ("Ispis" + param); } postupak javnog objekta () baca iznimku {Rezultat objekta = doSomething (); if (result == null) {throw new Exception ("Obrada nije uspjela. Dobio je null odgovor"); } else {vratiti rezultat; }}

The ispis () poziv metode samo bi se ispisao "Null" ali neće donijeti iznimku. Slično tome, postupak() nikad se ne bi vratio null u svom odgovoru. To radije baca Iznimka.

Dakle, za klijentski kôd koji pristupa gore navedenim API-ima, nema potrebe za null ček.

Međutim, takvi API-i moraju to izričito naznačiti u svom ugovoru. Zajedničko mjesto za API-je da objavljuju takav ugovor je JavaDoc.

To, međutim, daje nema jasne naznake API ugovora i stoga se oslanja na programere klijentskog koda kako bi osigurali njegovu usklađenost.

U sljedećem ćemo odjeljku vidjeti kako nekoliko IDE-a i drugi razvojni alati pomažu programerima u tome.

4. Automatizacija API ugovora

4.1. Korištenje statičke analize koda

Alati za statičku analizu koda pomažu u poboljšanju kvalitete koda. A nekoliko takvih alata također omogućuje programerima da održe null ugovor. Jedan od primjera je FindBugs.

FindBugs pomaže u upravljanju null ugovor putem @Nullable i @NonNull bilješke. Te bilješke možemo koristiti preko bilo koje metode, polja, lokalne varijable ili parametra. To klijentskom kodu izričito objašnjava može li biti označeni tip null ili ne. Pogledajmo primjer:

javna praznina prihvaća (@Nonnull Object param) {System.out.println (param.toString ()); }

Ovdje, @NonNull jasno govori da argument ne može biti null. Ako klijentski kod pozove ovu metodu bez provjere argumenta za null, FindBugs bi generirao upozorenje u vrijeme sastavljanja.

4.2. Korištenje IDE podrške

Programeri se općenito oslanjaju na IDE-ove za pisanje Java koda. A značajke poput pametnog dovršenja koda i korisnih upozorenja, poput kada varijabla možda nije dodijeljena, zasigurno pomažu u velikoj mjeri.

Neki IDE-i također omogućavaju programerima upravljanje API ugovorima i na taj način uklanjaju potrebu za alatom za statičku analizu koda. IntelliJ IDEA nudi @NonNull i @Nullable bilješke. Da bismo dodali podršku za ove bilješke u IntelliJ-u, moramo dodati sljedeću Mavenovu ovisnost:

 bilješke org.jetbrains 16.0.2 

Sada, IntelliJ će generirati upozorenje ako null ček nedostaje, kao u našem posljednjem primjeru.

IntelliJ također nudi a Ugovor napomena za rukovanje složenim API ugovorima.

5. Tvrdnje

Do sada smo razgovarali samo o uklanjanju potrebe za null provjere iz klijentskog koda. Ali, to je rijetko primjenjivo u stvarnim aplikacijama.

Ajmo sad pretpostavimo da radimo s API-jem koji ne može prihvatiti null parametre ili može vratiti a null odgovor koji mora riješiti klijent. Ovo predstavlja potrebu da provjerimo parametre ili odgovor za a null vrijednost.

Ovdje možemo koristiti Javne tvrdnje umjesto tradicionalnih null provjeri uvjetnu izjavu:

public void accept (objektni param) {assert param! = null; doSomething (param); }

U retku 2 provjeravamo postoji li a null parametar. Ako su tvrdnje omogućene, to bi rezultiralo AssertionError.

Iako je to dobar način za utvrđivanje preduvjeta poput ne-null parametri, ovaj pristup ima dva glavna problema:

  1. Tvrdnje su obično onemogućene u JVM-u
  2. A lažno tvrdnja rezultira neprovjerenom pogreškom koja je nepopravljiva

Stoga se programerima ne preporučuje korištenje tvrdnji za provjeru uvjeta. U sljedećim odjeljcima razgovarat ćemo o drugim načinima rukovanja null validacije.

6. Izbjegavanje Nula Provjerava kroz prakse kodiranja

6.1. Preduvjeti

Obično je dobra praksa rano pisanje koda. Stoga, ako API prihvaća više parametara koji to ne smiju biti null, bolje je provjeriti ima linull parametar kao preduvjet API-ja.

Na primjer, pogledajmo dvije metode - jednu koja rano ne uspije i drugu koja ne uspije:

javna praznina goodAccept (Niz jedan, Niz dva, Niz tri) {if (jedan == null || dva == null || tri == null) {throw new IllegalArgumentException (); } postupak (jedan); postupak (dva); postupak (tri); } javna void badAccept (Niz jedan, Niz dva, Niz tri) {if (jedan == null) {baciti novi IllegalArgumentException (); } else {postupak (jedan); } if (two == null) {throw new IllegalArgumentException (); } else {postupak (dva); } if (three == null) {throw new IllegalArgumentException (); } else {postupak (tri); }}

Jasno, trebali bismo radije goodAccept () nad badAccept ().

Kao alternativu možemo koristiti i Guavine preduvjete za provjeru valjanosti API parametara.

6.2. Korištenje primitiva umjesto klasa omota

Od null nije prihvatljiva vrijednost za primitivce poput int, trebali bismo ih preferirati u odnosu na njihove slične omote Cijeli broj kad god je moguće.

Razmotrimo dvije implementacije metode koja zbraja dvije cijele brojeve:

javna statička int primitiveSum (int a, int b) {return a + b; } javni statički Integer wrapperSum (Integer a, Integer b) {return a + b; }

Nazovimo ove API-je u našem klijentskom kodu:

int sum = primitiveSum (null, 2);

To bi rezultiralo pogreškom vremena prevođenja od null nije valjana vrijednost za int.

A kada koristimo API s klasama omotača, dobivamo NullPointerException:

assertThrows (NullPointerException.class, () -> wrapperSum (null, 2));

Postoje i drugi čimbenici za upotrebu primitiva nad omotima, kao što smo opisali u drugom vodiču, Java primitivi nasuprot objektima.

6.3. Prazne zbirke

Povremeno moramo vratiti kolekciju kao odgovor metode. Za takve metode uvijek bismo trebali pokušati vrati praznu zbirku umjesto null:

javna imena popisa () {if (userExists ()) {return Stream.of (readName ()). collect (Collectors.toList ()); } else {return Collections.emptyList (); }}

Stoga smo izbjegli potrebu da naš klijent izvede a null provjeri prilikom pozivanja ove metode.

7. Korištenje Predmeti

Java 7 predstavila je novo Predmeti API. Ovaj API ima nekoliko statički korisne metode koje oduzimaju puno suvišnih kodova. Pogledajmo jednu takvu metodu, requireNonNull ():

javna praznina prihvaća (parametar objekta) {Objects.requireNonNull (param); // učini nešto() }

Ajmo sada testirati prihvatiti() metoda:

assertThrows (NullPointerException.class, () -> prihvatiti (null));

Pa ako null se predaje kao argument, prihvatiti() baca a NullPointerException.

Ova klasa također ima isNull () i nonNull () metode koje se mogu koristiti kao predikati za provjeru objekta null.

8. Korištenje Neobvezno

8.1. Koristeći iliElseThrow

Java 8 predstavila je novu Neobvezno API na jeziku. Ovo nudi bolji ugovor za rukovanje neobaveznim vrijednostima u odnosu na null. Da vidimo kako Neobvezno oduzima potrebu za null provjere:

javni Izborni postupak (obrađena logička vrijednost) {String response = doSomething (obradeno); if (response == null) {return Izborno.empty (); } return Neobvezno.of (odgovor); } privatni niz doSomething (boolean obrađen) {if (obrađen) {return "pass"; } else {vratiti nulu; }}

Vraćanjem an Neobvezno, kao što je prikazano gore, the postupak metoda pozivatelju jasno daje do znanja da odgovor može biti prazan i da se njime mora rukovati u vrijeme sastavljanja.

To posebno uklanja potrebu za bilo kojim null provjere u kodu klijenta. Praznim se odgovorom može drugačije postupati pomoću deklarativnog stila Neobvezno API:

assertThrows (Exception.class, () -> process (false) .orElseThrow (() -> new Exception ()));

Nadalje, također pruža bolji ugovor programerima API-ja koji klijentima znači da API može vratiti prazan odgovor.

Iako smo eliminirali potrebu za a null provjerite pozivatelja ovog API-ja, koristili smo ga za vraćanje praznog odgovora. Da biste to izbjegli, Neobvezno pruža an odNullable metoda koja vraća Neobvezno s navedenom vrijednošću, ili prazan, ako je vrijednost null:

javni Izborni postupak (logička obrada) {String response = doSomething (obradeno); return Optional.ofNullable (odgovor); }

8.2. Koristeći Neobvezno sa Zbirkama

Dok se bavite praznim zbirkama, Neobvezno dobro dođe:

javni String findFirst () {return getList (). stream () .findFirst () .orElse (DEFAULT_VALUE); }

Ova bi funkcija trebala vratiti prvu stavku popisa. The Stream Apis findFirst funkcija će vratiti prazno Neobvezno kad nema podataka. Evo, koristili smo ili drugo umjesto toga pružiti zadanu vrijednost.

To nam omogućuje rukovanje praznim popisima ili popisima, koji nakon što upotrijebimo Stream knjižnica filtar metoda, nema predmeta za isporuku.

Alternativno, klijentu također možemo dopustiti da odluči kako postupati prazan povratkom Neobvezno iz ove metode:

javno Neobavezno findOptionalFirst () {return getList (). stream () .findFirst (); }

Stoga, ako je rezultat getList je prazno, ova metoda će vratiti prazno Neobvezno klijentu.

Koristeći Neobvezno with collection omogućuje nam dizajniranje API-ja koji će sigurno vratiti ne-null vrijednosti, izbjegavajući tako eksplicitne null provjere na klijentu.

Ovdje je važno napomenuti da se ova provedba oslanja getList ne vraćajući se null. Međutim, kao što smo raspravljali u prošlom odjeljku, često je bolje vratiti prazan popis, a ne null.

8.3. Kombiniranje fakultativnih zahtjeva

Kad se počnu vraćati naše funkcije Neobvezno potreban nam je način da kombiniramo njihove rezultate u jednu vrijednost. Uzmimo svoje getList primjer iz ranijeg. Što ako bi se vratio Neobvezno popisa, ili ih je trebalo omotati metodom koja je omotala a null s Neobvezno koristeći odNullable?

Naše findFirst metoda želi vratiti Neobvezno prvi element an Neobvezno popis:

javni Izborni optionalListFirst () {return getOptionalList () .flatMap (list -> list.stream (). findFirst ()); }

Korištenjem flatMap funkcija na Neobvezno vratio iz getOptional možemo raspakirati rezultat unutarnjeg izraza koji se vraća Neobvezno. Bez flatMap, rezultat bi bio Neobvezno. The flatMap operacija se izvodi samo kad Neobvezno nije prazna.

9. Knjižnice

9.1. Koristeći Lombok

Lombok je sjajna knjižnica koja smanjuje količinu standardnog koda u našim projektima. Dolazi s nizom bilješki koje zauzimaju mjesto uobičajenih dijelova koda koje često sami pišemo u Java programima, poput gettera, settera i toString (), da navedemo samo neke.

Još jedna od njegovih bilješki je @NonNull. Dakle, ako projekt već koristi Lombok za uklanjanje šifre tablice, @NonNull može zamijeniti potrebu za null provjere.

Prije nego što prijeđemo na neke primjere, dodajmo Mavenovu ovisnost za Lombok:

 org.projectlombok lombok 1.18.6 

Sada možemo koristiti @NonNull gdje god a null potrebna je provjera:

javna praznina prihvaća (@NonNull Object param) {System.out.println (param); }

Dakle, jednostavno smo označili objekt za koji null bila bi potrebna provjera, a Lombok generira kompiliranu klasu:

public void accept (@NonNull Object param) {if (param == null) {throw new NullPointerException ("param"); } else {System.out.println (param); }}

Ako param je null, ova metoda baca a NullPointerException. Metoda to mora izričito objasniti u ugovoru, a klijentski kod mora obraditi iznimku.

9.2. Koristeći StringUtils

Općenito, Niz provjera valjanosti uključuje provjeru prazne vrijednosti null vrijednost. Stoga bi uobičajena izjava o provjeri valjanosti bila:

javna praznina prihvaća (string param) {if (null! = param &&! param.isEmpty ()) System.out.println (param); }

To brzo postaje suvišno ako se moramo nositi s puno toga Niz vrste. Ovo je gdje StringUtils dobro dođe. Prije nego što ovo vidimo na djelu, dodajmo Mavenovu ovisnost za commons-lang3:

 org.apache.commons commons-lang3 3.8.1 

Ajmo sada refaktorizirati gornji kod s StringUtils:

javna praznina prihvaća (parametar niza) {if (StringUtils.isNotEmpty (param)) System.out.println (param); }

Dakle, zamijenili smo svoje null ili prazan ček s statički korisna metoda isNotEmpty (). Ovaj API nudi druge moćne korisne metode za rukovanje uobičajenim Niz funkcije.

10. Zaključak

U ovom smo članku pogledali razne razloge za NullPointerException i zašto je to teško prepoznati. Tada smo vidjeli razne načine kako izbjeći suvišnost koda oko provjere null s parametrima, vrstama povrata i ostalim varijablama.

Svi primjeri dostupni su na GitHubu.