Unngå Sjekk For Null-Setning I Java
Oversikt
generelt er nullvariabler, referanser og samlinger vanskelig å håndtere I Java-kode. Ikke bare er de vanskelige å identifisere, men de er også komplekse å håndtere.faktisk kan noen savner i å håndtere null ikke identifiseres ved kompileringstid og resulterer i En NullPointerException ved kjøretid.I denne opplæringen tar vi en titt på behovet for å sjekke om null I Java og ulike alternativer som hjelper oss med å unngå nullkontroller i koden vår.
Further reading:
Using NullAway to Avoid NullPointerExceptions
Spring Null-Safety Annotations
Introduction to the Null Object Pattern
What Is NullPointerException?
I Henhold Til Javadoc for NullPointerException, kastes den når et program forsøker å bruke null i et tilfelle der et objekt er nødvendig, for eksempel:
- Kaller en forekomstmetode for et nullobjekt
- Tilgang til Eller endre et felt av et nullobjekt
- Tar lengden på null som om det var en matrise
- Tilgang Til Eller endre nullsporene som om det var en matrise
- Kaster null som om det Var en matrise Hvis det var en kastbar verdi
La oss raskt se noen eksempler på java-koden som forårsaker dette unntaket:
public void doSomething() { String result = doSomethingElse(); if (result.equalsIgnoreCase("Success")) // success }}private String doSomethingElse() { return null;}
her prøvde Vi å påkalle et metodekall for nullreferanse. Dette vil resultere i En NullPointerException.
Et annet vanlig eksempel er hvis vi prøver å få tilgang til en null array:
public static void main(String args) { findMax(null);}private static void findMax(int arr) { int max = arr; //check other elements in loop}
dette fører Til En NullPointerException på linje 6.
dermed får tilgang til et felt, en metode eller en indeks for et nullobjekt En NullPointerException, som det kan ses fra eksemplene ovenfor.
en vanlig måte å unngå NullPointerException på er å sjekke om null:
public void doSomething() { String result = doSomethingElse(); if (result != null && result.equalsIgnoreCase("Success")) { // success } else // failure}private String doSomethingElse() { return null;}
i den virkelige verden finner programmerere det vanskelig å identifisere hvilke objekter som kan være null. En aggressivt sikker strategi kan være å sjekke null for hvert objekt. Dette forårsaker imidlertid mange overflødige nullkontroller og gjør koden mindre lesbar.
i de neste seksjonene går vi gjennom noen av alternativene I Java som unngår slik redundans.
Håndtering av null GJENNOM API-Kontrakten
som omtalt i den siste delen, forårsaker tilgang til metoder eller variabler av nullobjekter En NullPointerException. Vi diskuterte også at å sette nullkontroll på et objekt før du får tilgang til det, eliminerer muligheten for NullPointerException.
imidlertid er Det Ofte Apier som kan håndtere nullverdier. For eksempel:
public void print(Object param) { System.out.println("Printing " + param);}public Object process() throws Exception { Object result = doSomething(); if (result == null) { throw new Exception("Processing fail. Got a null response"); } else { return result; }}
skriv ut () metodekall ville bare skrive ut «null», men vil ikke kaste et unntak. På samme måte vil prosess () aldri returnere null i sitt svar. Det kaster heller Et Unntak.
Så for en klientkode som får tilgang til Apiene ovenfor, er det ikke behov for nullkontroll.
slike Apier må imidlertid gjøre det eksplisitt i kontrakten. Et felles Sted For Apier å publisere en slik kontrakt Er JavaDoc.
dette gir imidlertid ingen klar indikasjon PÅ API-kontrakten og er dermed avhengig av klientkodeutviklerne for å sikre overholdelse.
i neste avsnitt ser vi hvordan noen Ideer og andre utviklingsverktøy hjelper utviklere med dette.
Automatisere API-Kontrakter
4.1. Ved Hjelp Av Statisk Kodeanalyse
Bidrar Statiske kodeanalyseverktøy til å forbedre kodekvaliteten til en god del. Og noen få slike verktøy tillater også utviklerne å opprettholde nullkontrakten. Et eksempel er FindBugs.FindBugs hjelper med å administrere nullkontrakten gjennom @Nullable og @NonNull-merknadene. Vi kan bruke disse merknadene over hvilken som helst metode, felt, lokal variabel eller parameter. Dette gjør det eksplisitt for klientkoden om den annoterte typen kan være null eller ikke. La oss se et eksempel:
public void accept(@Nonnull Object param) { System.out.println(param.toString());}
Her gjør @NonNull Det klart At argumentet ikke kan være null. Hvis klientkoden kaller denne metoden uten å sjekke argumentet for null, Vil FindBugs generere en advarsel ved kompileringstid.
4.2. Bruke IDE-Støtte
Utviklere stoler generelt på Ide for å skrive Java-kode. Og funksjoner som smart kodefullføring og nyttige advarsler, som når en variabel kanskje ikke har blitt tildelt, hjelper sikkert i stor grad.
Noen Ide tillater også utviklere å administrere API-kontrakter og dermed eliminere behovet for et statisk kodeanalyseverktøy. IntelliJ IDEA gir @NonNull og @Nullable merknader. For å legge til støtte for disse merknadene I IntelliJ, må vi legge til Følgende maven avhengighet:
<dependency> <groupId>org.jetbrains</groupId> <artifactId>annotations</artifactId> <version>16.0.2</version></dependency>
Nå Vil IntelliJ generere en advarsel hvis nullkontrollen mangler, som i vårt siste eksempel.
IntelliJ gir også En Kontrakt merknad for håndtering av komplekse API kontrakter.
5. Påstander
Inntil nå har vi bare snakket om å fjerne behovet for nullkontroller fra klientkoden. Men det er sjelden aktuelt i virkelige applikasjoner.Nå, la oss anta at vi jobber med EN API som ikke kan godta nullparametere eller kan returnere et nullsvar som må håndteres av klienten. Dette presenterer behovet for oss å sjekke parametrene eller svaret for en nullverdi.
Her kan Vi bruke Java-Påstander i stedet for den tradisjonelle nullkontroll betingede setningen:
public void accept(Object param){ assert param != null; doSomething(param);}
i linje 2 ser vi etter en nullparameter. Hvis påstandene er aktivert, vil dette resultere i En Påståelsesfeil.
Selv om det er en god måte å hevde forutsetninger som ikke-null-parametere, har denne tilnærmingen to store problemer:
- Påstander er vanligvis deaktivert i EN JVM
- en falsk påstand resulterer i en ukontrollert feil som er uopprettelig
derfor anbefales det ikke for programmerere å bruke Påstander for å sjekke forhold. I de følgende avsnittene diskuterer vi andre måter å håndtere nullvalideringer på.
Unngå Null Sjekker Gjennom Koding Praksis
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 å være null, er det bedre å sjekke for hver ikke-null parameter som en forutsetning FOR API.for eksempel, la oss 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(); } process(one); process(two); process(three);}public void badAccept(String one, String two, String three) { if (one == null) { throw new IllegalArgumentException(); } else { process(one); } if (two == null) { throw new IllegalArgumentException(); } else { process(two); } if (three == null) { throw new IllegalArgumentException(); } else { process(three); }}
Klart, Vi bør foretrekke goodAccept() over badAccept().
som et alternativ kan Vi også bruke Guavas Forutsetninger for å validere API-parametere.
6.2. Ved Å bruke Primitiver I Stedet for Wrapper Klasser
siden null ikke er en akseptabel verdi for primitiver som int, bør vi foretrekke dem over deres wrapper-kolleger som Heltall der det er mulig.
Vurder 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 nå kalle Disse Apiene i vår klientkode:
int sum = primitiveSum(null, 2);
dette vil resultere i en kompileringstid feil siden null ikke er en gyldig verdi for en int.
Og når DU bruker API med wrapper klasser, får Vi En NullPointerException:
assertThrows(NullPointerException.class, () -> wrapperSum(null, 2));
Det er også andre faktorer for å bruke primitiver over wrappers, som vi dekket i en annen opplæring, Java Primitiver versus Objekter.
6.3. Tomme Samlinger
Av og til må vi returnere en samling som et svar fra en metode. For slike metoder, bør vi alltid prøve å returnere en tom samling i stedet for null:
public List<String> names() { if (userExists()) { return Stream.of(readName()).collect(Collectors.toList()); } else { return Collections.emptyList(); }}
Derfor har vi unngått behovet for vår klient til å utføre en null-sjekk når du ringer denne metoden.
Bruke Objekter
Java 7 introduserte Den nye Objekter API. DENNE API har flere statiske verktøy metoder som tar bort mye overflødig kode. La oss se på en slik metode, requireNonNull ():
public void accept(Object param) { Objects.requireNonNull(param); // doSomething()}
nå, la oss teste accept () – metoden:
assertThrows(NullPointerException.class, () -> accept(null));
så, hvis null er 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 for null.
Ved Hjelp Av Valgfri
8.1. Bruke orElseThrow
Java 8 innført en ny Valgfri API i språket. Dette gir en bedre kontrakt for håndtering av valgfrie verdier i forhold til null. La Oss se Hvordan Valgfritt tar bort behovet for nullkontroller:
public Optional<Object> process(boolean processed) { String response = doSomething(processed); if (response == null) { return Optional.empty(); } return Optional.of(response);}private String doSomething(boolean processed) { if (processed) { return "passed"; } else { return null; }}
ved å returnere En Valgfri, som vist ovenfor, gjør prosessmetoden det klart for den som ringer at svaret kan være tomt og må håndteres ved kompileringstid.
dette tar spesielt bort behovet for nullkontroller i klientkoden. En tom respons kan håndteres annerledes ved hjelp av deklarativ stil Av Den Valgfrie API:
assertThrows(Exception.class, () -> process(false).orElseThrow(() -> new Exception()));
Videre gir DET også EN bedre kontrakt TIL API-utviklere for å betegne klientene at EN API kan returnere et tomt svar.
Selv om vi eliminert behovet for nullkontroll på den som ringer TIL DENNE API-EN, brukte vi den til å returnere et tomt svar. For å unngå Dette, Gir Optional en ofNullable metode som returnerer En Valgfri med den angitte verdien, eller tom, hvis verdien er null:
public Optional<Object> process(boolean processed) { String response = doSomething(processed); return Optional.ofNullable(response);}
8.2. Bruke Valgfritt Med Samlinger
Mens du arbeider med tomme samlinger, Kommer Valgfritt til nytte:
public String findFirst() { return getList().stream() .findFirst() .orElse(DEFAULT_VALUE);}
denne funksjonen skal returnere det første elementet i en liste. Stream API findFirst-funksjonen returnerer en tom Valgfri når det ikke er noen data. Her har vi brukt orElse til å gi en standardverdi i stedet.
dette tillater oss å håndtere enten tomme lister, eller lister, som etter at Vi har brukt Stream bibliotekets filtermetode, har ingen elementer å levere.
Alternativt kan vi også la klienten bestemme hvordan han skal håndtere tom ved Å returnere Valgfritt fra denne metoden:
public Optional<String> findOptionalFirst() { return getList().stream() .findFirst();}
derfor, hvis resultatet av getList er tomt, vil denne metoden returnere et tomt Valgfritt til klienten.
Ved Å bruke Valgfritt med samlinger kan Vi designe Apier som er sikker på å returnere ikke-nullverdier, og dermed unngå eksplisitte nullkontroller på klienten.
det er viktig å merke seg at denne implementeringen er avhengig av at getList ikke returnerer null. Men som vi diskuterte i den siste delen, er det ofte bedre å returnere en tom liste i stedet for en null.
8.3. Kombinere Optionals
Når vi begynner å gjøre våre funksjoner tilbake Valgfritt, trenger vi 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 med en metode som pakket inn en null Med Valgfri bruk av nullable?
vår findFirst-metode vil returnere Et Valgfritt første element i En Valgfri liste:
public Optional<String> optionalListFirst() { return getOptionalList() .flatMap(list -> list.stream().findFirst());}
ved å bruke flatMap-funksjonen på Valgfritt returnert fra getOptional kan vi pakke ut resultatet av et indre uttrykk som returnerer Valgfritt. Uten flatMap vil resultatet Være Valgfritt< Valgfritt <Streng >>. FlatMap-operasjonen utføres bare når Tilleggsutstyret ikke er tomt.
Biblioteker
9.1. Bruke Lombok
Lombok er et flott bibliotek som reduserer mengden av standardtekst kode i våre prosjekter. Den leveres med et sett med merknader som tar plassen til vanlige deler av koden vi ofte skriver oss Selv I Java-programmer, for eksempel getters, settere, og toString(), for å nevne noen.
en annen av sine merknader er @NonNull. Så hvis et prosjekt allerede bruker Lombok for å eliminere standardtekstkode, kan @NonNull erstatte behovet for nullkontroller.
før vi går videre for å se noen eksempler, la oss legge Til en maven-avhengighet for Lombok:
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.6</version></dependency>
nå kan vi bruke @NonNull hvor en nullkontroll er nødvendig:
public void accept(@NonNull Object param){ System.out.println(param);}
så vi bare kommenterte objektet som nullkontrollen ville vært nødvendig for, og Lombok genererer den kompilerte klassen:
public void accept(@NonNull Object param) { if (param == null) { throw new NullPointerException("param"); } else { System.out.println(param); }}
hvis param er null, kaster denne metoden en nullpointerexception. Metoden må gjøre dette eksplisitt i kontrakten, og klientkoden må håndtere unntaket.
9.2. Ved Hjelp Av StringUtils
Generelt Inneholder Strengvalidering en sjekk for en tom verdi i tillegg til nullverdi. Derfor vil en vanlig valideringserklæring være:
public void accept(String param){ if (null != param && !param.isEmpty()) System.out.println(param);}
dette blir raskt overflødig hvis vi må håndtere mange Strengtyper. Det er her StringUtils kommer hendig. Før vi ser dette i aksjon, la oss legge til en maven-avhengighet for commons-lang3:
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.11</version></dependency>
La oss nå refactor koden ovenfor Med StringUtils:
public void accept(String param) { if (StringUtils.isNotEmpty(param)) System.out.println(param);}
så, vi erstattet vår null eller tom sjekk med en statisk verktøymetode isNotEmpty (). DENNE API tilbyr andre kraftige verktøy metoder for håndtering av vanlige Strengfunksjoner.
Konklusjon
i denne artikkelen så vi på de ulike årsakene Til NullPointerException og hvorfor det er vanskelig å identifisere. Deretter så vi ulike måter å unngå redundans i kode rundt å sjekke for null med parametere, returtyper og andre variabler.
alle eksemplene er tilgjengelige på GitHub.
Kom i gang Med Våren 5 Og Våren Boot 2, Gjennom Lær Våren kurset:
>> SJEKK UT KURSET