Unngå å sjekke om null uttalelse i Java

1. Oversikt

Som regel, null variabler, referanser og samlinger er vanskelige å håndtere i Java-kode. Ikke bare er de vanskelig å identifisere, men de er også kompliserte å håndtere.

Faktisk, noen glipp i å håndtere null kan ikke identifiseres ved kompileringstid og resulterer i en NullPointerException ved kjøretid.

I denne opplæringen vil vi se på behovet for å se etter null i Java og ulike alternativer som hjelper oss å unngå null sjekker inn koden vår.

2. Hva er? NullPointerException?

I følge Javadoc for NullPointerException, blir den kastet når et program prøver å bruke null i et tilfelle der det kreves et objekt, for eksempel:

  • Kaller en forekomstmetode for en null gjenstand
  • Få tilgang til eller endre et felt av en null gjenstand
  • Tar lengden på null som om det var en matrise
  • Åpne eller endre sporene til null som om det var en matrise
  • Kaste null som om det var en Kastbar verdi

La oss raskt se noen få eksempler på Java-koden som forårsaker dette unntaket:

public void doSomething () {Strengresultat = doSomethingElse (); hvis (result.equalsIgnoreCase ("Suksess")) // suksess}} privat streng doSomethingElse () {return null; }

Her, Vi prøvde å påkalle en metodekall for en null henvisning. Dette vil resultere i en NullPointerException.

Et annet vanlig eksempel er hvis vi prøver å få tilgang til a null matrise:

public static void main (String [] args) {findMax (null); } privat statisk tomrom findMax (int [] arr) {int max = arr [0]; // sjekk andre elementer i løkke}

Dette fører til en NullPointerException på linje 6.

Dermed får du tilgang til hvilket som helst felt, metode eller indeks for en null objekt forårsaker en NullPointerException, som det kan sees fra eksemplene ovenfor.

En vanlig måte å unngå NullPointerException er å sjekke etter null:

public void doSomething () {String result = doSomethingElse (); hvis (resultat! = null && resultat.equalsIgnoreCase ("Suksess")) {// suksess} annet // feil} privat streng doSomethingElse () {return null; }

I den virkelige verden har programmerere det vanskelig å identifisere hvilke objekter som kan være null. En aggressivt trygg strategi kan være å sjekke null for hvert objekt. Dette fører imidlertid til mye overflødig null sjekker og gjør koden vår mindre lesbar.

I de neste par seksjonene vil vi gå gjennom noen av alternativene i Java som unngår slik redundans.

3. Håndtering null Gjennom API-kontrakten

Som diskutert i forrige avsnitt, tilgang til metoder eller variabler av null gjenstander forårsaker en NullPointerException. Vi diskuterte også at å sette en null sjekke et objekt før du får tilgang til det eliminerer muligheten for NullPointerException.

Imidlertid er det ofte APIer som kan håndtere null verdier. For eksempel:

public void print (Object param) {System.out.println ("Printing" + param); } offentlig objektprosess () kaster Unntak {Objektresultat = gjørSomething (); hvis (resultat == null) {kast nytt unntak ("Behandlingen mislykkes. Fikk null svar"); } annet {returresultat; }}

De skrive ut() metodeanrop ville bare skrevet ut "null" men vil ikke kaste et unntak. På samme måte, prosess() ville aldri komme tilbake null i sitt svar. Det kaster heller en Unntak.

Så for en klientkode som får tilgang til ovennevnte API-er, er det ikke behov for en null Sjekk.

Imidlertid må slike API-er gjøre det eksplisitt i kontrakten. Et vanlig sted for APIer å publisere en slik kontrakt er JavaDoc.

Dette gir imidlertid ingen klar indikasjon på API-kontrakten og er derfor avhengig av klientkodeutviklerne for å sikre at den overholdes.

I neste avsnitt ser vi hvordan noen få IDEer og andre utviklingsverktøy hjelper utviklere med dette.

4. Automatisering av API-kontrakter

4.1. Bruk av statisk kodeanalyse

Verktøy for analyse av statisk kode bidrar til å forbedre kodekvaliteten til en god del. Og noen få slike verktøy tillater også utviklerne å vedlikeholde null kontrakt. Et eksempel er FindBugs.

FindBugs hjelper deg med å administrere null kontrakt gjennom @Nullable og @NonNull kommentarer. Vi kan bruke disse merknadene over hvilken som helst metode, felt, lokal variabel eller parameter. Dette gjør det eksplisitt for klientkoden om den merkede typen kan være null eller ikke. La oss se et eksempel:

public void accept (@Nonnull Object param) {System.out.println (param.toString ()); }

Her, @NonNull gjør det klart at argumentet ikke kan være null. Hvis klientkoden kaller denne metoden uten å sjekke argumentet for null, FindBugs vil generere en advarsel ved kompileringstidspunktet.

4.2. Bruke IDE-støtte

Utviklere stoler vanligvis på IDEer for å skrive Java-kode. Og funksjoner som fullføring av smart kode og nyttige advarsler, som når en variabel kanskje ikke har blitt tildelt, hjelper absolutt i stor grad.

Noen IDEer tillater også utviklere å administrere API-kontrakter og dermed eliminere behovet for et verktøy for analyse av statisk kode. IntelliJ IDEA tilbyr @NonNull og @Nullable kommentarer. For å legge til støtten for disse kommentarene i IntelliJ, må vi legge til følgende Maven-avhengighet:

 org.jetbrains-merknader 16.0.2 

Nå, IntelliJ vil generere en advarsel hvis null sjekk mangler, som i vårt siste eksempel.

IntelliJ gir også en Kontrakt kommentar for håndtering av komplekse API-kontrakter.

5. Påstander

Inntil nå har vi bare snakket om å fjerne behovet for null sjekker fra klientkoden. Men det er sjelden anvendelig i virkelige applikasjoner.

La oss nå anta at vi jobber med et API som ikke kan akseptere null parametere eller kan returnere a null svar som må håndteres av klienten. Dette presenterer behovet for oss å sjekke parametrene eller svaret for en null verdi.

Her kan vi bruke Java Assertions i stedet for det tradisjonelle null sjekk betinget uttalelse:

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

I linje 2 ser vi etter en null parameter. Hvis påstandene er aktivert, vil dette resultere i en Påstand Feil.

Selv om det er en god måte å hevde forutsetninger som ikke-null parametere, denne tilnærmingen har to store problemer:

  1. Påstander er vanligvis deaktivert i en JVM
  2. EN falsk påstand resulterer i en ukontrollert feil som er uopprettelig

Derfor anbefales det ikke for programmerere å bruke påstander for å kontrollere forholdene. I de følgende avsnittene vil vi diskutere andre måter å håndtere på null valideringer.

6. Unngå Null Sjekker gjennom kodingspraksis

6.1. Forutsetninger

Det er vanligvis en god praksis å skrive kode som mislykkes tidlig. Derfor, hvis en API godtar flere parametere som ikke er tillatt null, det er bedre å sjekke for alle ikke-null parameter som en forutsetning for API.

La oss for eksempel se på to metoder - en som mislykkes tidlig, og en som ikke gjør det:

public void goodAccept (String one, String two, String three) {if (one == null || two == null || three == null) {throw new IllegalArgumentException (); } prosess (en); prosess (to); prosess (tre); } public void badAccept (String one, String two, String three) {if (one == null) {throw new IllegalArgumentException (); } annet {prosess (en); } if (two == null) {throw new IllegalArgumentException (); } annet {prosess (to); } if (three == null) {throw new IllegalArgumentException (); } annet {prosess (tre); }}

Vi burde helt klart foretrekke det goodAccept () over badAccept ().

Som et alternativ kan vi også bruke Guavas forutsetninger for validering av API-parametere.

6.2. Bruke primitiver i stedet for emballasjeklasser

Siden null er ikke en akseptabel verdi for primitiver som int, vi burde foretrekke dem fremfor deres motstykker som Heltall der det er mulig.

Tenk på to implementeringer av en metode som summerer to heltall:

public static int primitiveSum (int a, int b) {return a + b; } public static Integer wrapperSum (Integer a, Integer b) {return a + b; }

La oss kalle disse API-ene i klientkoden vår:

int sum = primitiveSum (null, 2);

Dette vil resultere i en kompilasjonsfeil siden null er ikke en gyldig verdi for en int.

Og når vi bruker API med wrapper-klasser, får vi en NullPointerException:

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

Det er også andre faktorer for bruk av primitiver over innpakninger, som vi dekket i en annen opplæring, Java Primitives versus Objects.

6.3. Tomme samlinger

Noen ganger må vi returnere en samling som et svar fra en metode. For slike metoder, bør vi alltid prøve å returner en tom samling i stedet for null:

offentlige listenavn () {if (userExists ()) {return Stream.of (readName ()). collect (Collectors.toList ()); } annet {returner Collections.emptyList (); }}

Derfor har vi unngått behovet for at vår klient skal utføre en null sjekk når du kaller denne metoden.

7. Bruke Objekter

Java 7 introduserte det nye Objekter API. Denne API-en har flere statisk verktøymetoder som tar bort mye overflødig kode. La oss se på en slik metode, requireNonNull ():

public void accept (Object param) {Objects.requireNonNull (param); // gjør noe() }

La oss nå teste aksepterer() metode:

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

Så hvis null blir bestått som et argument, aksepterer() kaster en NullPointerException.

Denne klassen har også isNull () og nonNull () metoder som kan brukes som predikater for å sjekke et objekt null.

8. Bruke Valgfri

8.1. Ved hjelp av eller ElseTrow

Java 8 introduserte en ny Valgfri API på språket. Dette gir en bedre kontrakt for håndtering av valgfrie verdier sammenlignet med null. La oss se hvordan Valgfri tar bort behovet for null sjekker:

offentlig Valgfri prosess (boolsk behandlet) {Strengrespons = gjør noe (behandlet); if (response == null) {return Optional.empty (); } returner Optional.of (respons); } privat streng doSomething (boolsk behandlet) {hvis (behandlet) {retur "bestått"; } annet {return null; }}

Ved å returnere en Valgfri, som vist ovenfor, de prosess metoden gjør det klart for innringeren at svaret kan være tomt og må håndteres på kompileringstidspunktet.

Dette fjerner spesielt behovet for noe null sjekker i klientkoden. Et tomt svar kan håndteres annerledes ved å bruke den deklarative stilen til Valgfri API:

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

Videre gir det også en bedre kontrakt til API-utviklere for å signere til klientene at en API kan returnere et tomt svar.

Selv om vi fjernet behovet for en null sjekk innringeren av denne APIen, vi brukte den til å returnere et tomt svar. For å unngå dette, Valgfri gir en avNullable metode som returnerer en Valgfri med den angitte verdien, eller tømme, hvis verdien er null:

offentlig Valgfri prosess (boolsk behandlet) {Strengrespons = doSomething (behandlet); retur Optional.ofNullable (respons); }

8.2. Ved hjelp av Valgfri med samlinger

Mens du arbeider med tomme samlinger, Valgfri kommer godt med:

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

Denne funksjonen skal returnere det første elementet i en liste. De Strøm API-er findFirst funksjonen vil returnere en tom Valgfri når det ikke er data. Her har vi brukt ellers for å gi en standardverdi i stedet.

Dette lar oss håndtere enten tomme lister eller lister, som etter at vi har brukt Strøm bibliotekets filter metoden, har ingen varer å levere.

Alternativt kan vi også la klienten bestemme hvordan han skal håndtere tømme ved å returnere Valgfri fra denne metoden:

offentlig Valgfri findOptionalFirst () {return getList (). stream () .findFirst (); }

Derfor, hvis resultatet av getList er tom, vil denne metoden returnere en tom Valgfri til klienten.

Ved hjelp av Valgfri med samlinger kan vi designe API-er som helt sikkert returnerer verdier som ikke er null, og dermed unngå eksplisitte null sjekker på klienten.

Det er viktig å merke seg her at denne implementeringen er avhengig av getList kommer ikke tilbake null. Imidlertid, som vi diskuterte i forrige avsnitt, er det ofte bedre å returnere en tom liste i stedet for en null.

8.3. Kombinere tilleggsutstyr

Når vi begynner å få funksjonene våre tilbake Valgfri vi trenger en måte å kombinere resultatene til en enkelt verdi. La oss ta vår getList eksempel fra tidligere. Hva om det skulle returnere en Valgfri liste, eller skulle pakkes inn med en metode som pakket inn en null med Valgfri ved hjelp av avNullable?

Våre findFirst metoden ønsker å returnere en Valgfri første element i en Valgfri liste:

public Optional optionalListFirst () {return getOptionalList () .flatMap (list -> list.stream (). findFirst ()); }

Ved å bruke flatMap funksjon på Valgfri returnerte fra getOptional vi kan pakke ut resultatet av et indre uttrykk som kommer tilbake Valgfri. Uten flatMap, ville resultatet bli Valgfri. De flatMap operasjonen utføres bare når Valgfri er ikke tom.

9. Biblioteker

9.1. Bruke Lombok

Lombok er et flott bibliotek som reduserer mengden kokerplatekode i våre prosjekter. Den kommer med et sett med merknader som tar plass til vanlige deler av koden vi ofte skriver selv i Java-applikasjoner, for eksempel getters, setters og toString (), for å nevne noen.

En annen av kommentarene er @NonNull. Så hvis et prosjekt allerede bruker Lombok til å eliminere kjeleplatekoden, @NonNull kan erstatte behovet for null sjekker.

Før vi fortsetter for å se noen eksempler, la oss legge til en Maven-avhengighet for Lombok:

 org.projectlombok lombok 1.18.6 

Nå kan vi bruke @NonNull uansett hvor null sjekk er nødvendig:

public void accept (@NonNull Object param) {System.out.println (param); }

Så, vi bare kommenterte objektet som null sjekk ville ha vært påkrevd, og Lombok genererer den kompilerte klassen:

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

Hvis param er null, denne metoden kaster a NullPointerException. Metoden må gjøre dette eksplisitt i kontrakten, og klientkoden må håndtere unntaket.

9.2. Ved hjelp av StringUtils

Som regel, String validering inkluderer en sjekk for en tom verdi i tillegg til null verdi. Derfor vil en vanlig valideringserklæring være:

public void accept (String param) {if (null! = param &&! param.isEmpty ()) System.out.println (param); }

Dette blir fort overflødig hvis vi må takle mye av String typer. Dette er hvor StringUtils kommer hendig. Før vi ser dette i aksjon, la oss legge til en Maven-avhengighet for commons-lang3:

 org.apache.commons commons-lang3 3.8.1 

La oss nå omformulere koden ovenfor med StringUtils:

public void accept (String param) {if (StringUtils.isNotEmpty (param)) System.out.println (param); }

Så vi byttet ut vårt null eller tom sjekk med en statisk verktøymetode isNotEmpty (). Denne API-en tilbyr andre kraftige verktøymetoder for håndtering av vanlige String funksjoner.

10. Konklusjon

I denne artikkelen så vi på de forskjellige årsakene til NullPointerException og hvorfor det er vanskelig å identifisere. Så så vi forskjellige måter å unngå overflødighet i kode rundt å sjekke etter null med parametere, returtyper og andre variabler.

Alle eksemplene er tilgjengelige på GitHub.


$config[zx-auto] not found$config[zx-overlay] not found