Undgå Check for Null Statement i Java

1. Oversigt

Generelt, nul variabler, referencer og samlinger er vanskelige at håndtere i Java-kode. Ikke kun er de svære at identificere, men de er også komplekse at håndtere.

Som en kendsgerning, enhver miss i at håndtere nul kan ikke identificeres på kompileringstidspunktet og resulterer i en NullPointerException ved kørselstid.

I denne vejledning ser vi på behovet for at kontrollere, om der er tale om nul i Java og forskellige alternativer, der hjælper os med at undgå nul kontrollerer vores kode.

2. Hvad er? NullPointerException?

Ifølge Javadoc for NullPointerException, kastes det, når et program forsøger at bruge nul i et tilfælde, hvor der kræves et objekt, såsom:

  • Opkald til en instansmetode for en nul objekt
  • Adgang til eller ændring af et felt i en nul objekt
  • Tager længden af nul som om det var en matrix
  • Adgang til eller ændring af slots på nul som om det var en matrix
  • Kaste nul som om det var en Kan kastes værdi

Lad os hurtigt se et par eksempler på Java-koden, der forårsager denne undtagelse:

public void doSomething () {String result = doSomethingElse (); hvis (result.equalsIgnoreCase ("Succes")) // succes}} privat streng doSomethingElse () {return null; }

Her, vi forsøgte at påkalde en metodeopfordring til en nul reference. Dette ville resultere i en NullPointerException.

Et andet almindeligt eksempel er, hvis vi forsøger at få adgang til en nul matrix:

public static void main (String [] args) {findMax (null); } privat statisk tomrum findMax (int [] arr) {int max = arr [0]; // tjek andre elementer i loop}

Dette forårsager en NullPointerException på linje 6.

Således får du adgang til ethvert felt, metode eller indeks for en nul objekt forårsager en NullPointerExceptionsom det kan ses fra eksemplerne ovenfor.

En almindelig måde at undgå NullPointerException er at kontrollere for nul:

public void doSomething () {String result = doSomethingElse (); hvis (resultat! = null && resultat.equalsIgnoreCase ("Succes")) {// succes} andet // fiasko} privat streng doSomethingElse () {return null; }

I den virkelige verden har programmører svært ved at identificere, hvilke objekter der kan være nul. En aggressivt sikker strategi kan være at kontrollere nul for hvert objekt. Dette medfører dog meget overflødig nul kontrollerer og gør vores kode mindre læselig.

I de næste par sektioner gennemgår vi nogle af de alternativer i Java, der undgår sådan redundans.

3. Håndtering nul Gennem API-kontrakten

Som diskuteret i det sidste afsnit, adgang til metoder eller variabler af nul genstande forårsager en NullPointerException. Vi diskuterede også at sætte en nul Kontroller et objekt, før du får adgang til det, eliminerer muligheden for NullPointerException.

Imidlertid er der ofte API'er, der kan håndtere nul værdier. For eksempel:

offentlig tomrumsudskrivning (Objektparameter) {System.out.println ("Udskrivning" + param); } offentlig objektproces () kaster undtagelse {Objektresultat = doSomething (); hvis (resultat == null) {kast ny undtagelse ("Behandling mislykkedes. Fik et null svar"); } andet {returneres resultat; }}

Det Print() metodeopkald ville bare udskrive "nul" men kaster ikke en undtagelse. Tilsvarende behandle() ville aldrig vende tilbage nul i sit svar. Det smider snarere en Undtagelse.

Så for en klientkode, der har adgang til ovenstående API'er, er der ikke behov for en nul kontrollere.

Sådanne API'er skal dog gøre det eksplicit i deres kontrakt. Et fælles sted for API'er til at offentliggøre en sådan kontrakt er JavaDoc.

Dette giver dog ingen klar indikation af API-kontrakten og er derfor afhængig af klientkodeudviklerne for at sikre, at den overholdes.

I det næste afsnit vil vi se, hvordan et par IDE'er og andre udviklingsværktøjer hjælper udviklere med dette.

4. Automatisering af API-kontrakter

4.1. Brug af statisk kodeanalyse

Statiske kodeanalyseværktøjer hjælper med at forbedre kodekvaliteten til en hel del. Og et par sådanne værktøjer tillader også udviklerne at vedligeholde nul kontrakt. Et eksempel er FindBugs.

FindBugs hjælper med at administrere nul kontrakt gennem @Nullable og @NonNull kommentarer. Vi kan bruge disse bemærkninger over enhver metode, felt, lokal variabel eller parameter. Dette gør det eksplicit for klientkoden, om den kommenterede type kan være nul eller ikke. Lad os se et eksempel:

offentlig ugyldig accept (@Nonnull Object param) {System.out.println (param.toString ()); }

Her, @NonNull gør det klart, at argumentet ikke kan være nul. Hvis klientkoden kalder denne metode uden at kontrollere argumentet for nul, FindBugs genererer en advarsel på kompileringstidspunktet.

4.2. Brug af IDE Support

Udviklere er generelt afhængige af IDE'er til at skrive Java-kode. Og funktioner som smart kodefuldførelse og nyttige advarsler, som når en variabel muligvis ikke er tildelt, hjælper bestemt i høj grad.

Nogle IDE'er tillader også udviklere at administrere API-kontrakter og derved fjerne behovet for et værktøj til analyse af statisk kode. IntelliJ IDEA leverer @NonNull og @Nullable kommentarer. For at tilføje understøttelsen af ​​disse kommentarer i IntelliJ skal vi tilføje følgende Maven-afhængighed:

 annoteringer fra org.jetbrains 16.0.2 

Nu, IntelliJ genererer en advarsel, hvis nul check mangler, som i vores sidste eksempel.

IntelliJ leverer også en Kontrakt kommentar til håndtering af komplekse API-kontrakter.

5. Påstande

Indtil nu har vi kun talt om at fjerne behovet for nul kontrol fra klientkoden. Men det er sjældent anvendeligt i virkelige applikationer.

Lad os nu Antag, at vi arbejder med en API, der ikke kan acceptere nul parametre eller kan returnere a nul svar, der skal håndteres af klienten. Dette præsenterer behovet for os at kontrollere parametrene eller svaret for en nul værdi.

Her kan vi bruge Java Assertions i stedet for det traditionelle nul tjek betinget erklæring:

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

I linje 2 kontrollerer vi, om der er en nul parameter. Hvis påstandene er aktiveret, vil dette resultere i en Påstand Fejl.

Selv om det er en god måde at hævde præ-betingelser som ikke-nul parametre, denne tilgang har to store problemer:

  1. Påstande er normalt deaktiveret i en JVM
  2. EN falsk påstand resulterer i en ukontrolleret fejl, der er uoprettelig

Derfor anbefales det ikke for programmører at bruge påstande til kontrol af forhold. I de følgende afsnit diskuterer vi andre måder at håndtere nul valideringer.

6. Undgå Nul Kontrol gennem kodningspraksis

6.1. Forudsætninger

Det er normalt en god praksis at skrive kode, der fejler tidligt. Derfor, hvis en API accepterer flere parametre, som ikke er tilladt nul, det er bedre at kontrollere for alle ikke-nul parameter som en forudsætning for API'en.

Lad os for eksempel se på to metoder - en, der fejler tidligt, og en, der ikke gør det:

public void goodAccept (String one, String two, String three) {if (one == null || two == null || three == null) {kast nyt IllegalArgumentException (); } proces (en); proces (to); proces (tre); } public void badAccept (String one, String two, String three) {if (one == null) {throw new IllegalArgumentException (); } andet {proces (en); } if (two == null) {throw new IllegalArgumentException (); } andet {proces (to); } hvis (tre == null) {kast nyt IllegalArgumentException (); } andet {proces (tre); }}

Det er klart, at vi foretrækker det goodAccept () over badAccept ().

Som et alternativ kan vi også bruge Guavas forudsætninger til validering af API-parametre.

6.2. Brug af primitiver i stedet for indpakningsklasser

Siden nul er ikke en acceptabel værdi for primitiver som int, vi foretrækker dem frem for deres indpakningsmodeller som Heltal hvor det er muligt.

Overvej to implementeringer af en metode, der summerer to heltal:

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

Lad os nu kalde disse API'er i vores klientkode:

int sum = primitiveSum (null, 2);

Dette ville resultere i en kompileringstidsfejl siden nul er ikke en gyldig værdi for en int.

Og når vi bruger API'en med indpakningsklasser, får vi en NullPointerException:

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

Der er også andre faktorer til brug af primitiver over indpakninger, som vi dækkede i en anden tutorial, Java Primitives versus Objects.

6.3. Tomme samlinger

Lejlighedsvis er vi nødt til at returnere en samling som et svar fra en metode. For sådanne metoder skal vi altid prøve returnere en tom samling i stedet for nul:

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

Derfor har vi undgået behovet for vores klient til at udføre en nul tjek, når du kalder denne metode.

7. Brug Objekter

Java 7 introducerede det nye Objekter API. Denne API har flere statisk hjælpemetoder, der fjerner en masse overflødig kode. Lad os se på en sådan metode, requireNonNull ():

offentlig ugyldig accept (Object param) {Objects.requireNonNull (param); // gør noget() }

Lad os nu teste acceptere() metode:

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

Så hvis nul overføres som et argument, acceptere() kaster en NullPointerException.

Denne klasse har også isNull () og nonNull () metoder, der kan bruges som prædikater til at kontrollere et objekt for nul.

8. Brug Valgfri

8.1. Ved brug af eller ElseTrow

Java 8 introducerede en ny Valgfri API på sproget. Dette giver en bedre kontrakt til håndtering af valgfri værdier i forhold til nul. Lad os se hvordan Valgfri fjerner behovet for nul kontrol:

offentlig Valgfri proces (boolsk behandlet) {Strengrespons = doSomething (behandlet); hvis (svar == null) {return Optional.empty (); } returner Optional.of (svar); } privat streng doSomething (boolsk behandlet) {hvis (behandlet) {return "bestået"; } ellers {return null; }}

Ved at returnere en Valgfri, som vist ovenfor, det behandle metode gør det klart for den, der ringer op, at svaret kan være tomt og skal håndteres på kompileringstidspunktet.

Dette fjerner især behovet for noget nul kontrol i klientkoden. Et tomt svar kan håndteres forskelligt ved hjælp af den erklærende stil for Valgfri API:

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

Desuden giver det også en bedre kontrakt til API-udviklere for at betegne klienterne, at en API kan returnere et tomt svar.

Selvom vi fjernede behovet for en nul tjek indringeren af ​​denne API, vi brugte den til at returnere et tomt svar. For at undgå dette, Valgfri giver en ofNullable metode, der returnerer en Valgfri med den angivne værdi, eller tom, hvis værdien er nul:

offentlig Valgfri proces (boolsk behandlet) {Strengrespons = doSomething (behandlet); returnere Optional.ofNullable (svar); }

8.2. Ved brug af Valgfri med samlinger

Mens du beskæftiger dig med tomme samlinger, Valgfri kommer godt med:

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

Denne funktion skal returnere det første element på en liste. Det Strøm API'er findFirst funktion returnerer en tom Valgfri når der ikke er data. Her har vi brugt ellers for at angive en standardværdi i stedet.

Dette giver os mulighed for at håndtere enten tomme lister eller lister, som efter at vi har brugt Strøm biblioteks filter metode, har ingen varer at levere.

Alternativt kan vi også lade klienten beslutte, hvordan han skal håndtere tom ved at vende tilbage Valgfri fra denne metode:

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

Derfor, hvis resultatet af getList er tom, returnerer denne metode en tom Valgfri til klienten.

Ved brug af Valgfri med samlinger giver os mulighed for at designe API'er, der helt sikkert returnerer værdier, der ikke er nul, og dermed undgår eksplicit nul kontrollerer klienten.

Det er vigtigt at bemærke her, at denne implementering er afhængig af getList vender ikke tilbage nul. Som vi diskuterede i sidste afsnit, er det imidlertid ofte bedre at returnere en tom liste snarere end en nul.

8.3. Kombination af ekstraudstyr

Når vi begynder at vende tilbage til vores funktioner Valgfri vi har brug for en måde at kombinere deres resultater i en enkelt værdi. Lad os tage vores getList eksempel fra tidligere. Hvad hvis det var at returnere en Valgfri liste, eller skulle pakkes ind med en metode, der pakket en nul med Valgfri ved brug af ofNullable?

Vores findFirst metode ønsker at returnere en Valgfri første element i en Valgfri liste:

offentlig Valgfri optionalListFirst () {return getOptionalList () .flatMap (liste -> list.stream (). findFirst ()); }

Ved hjælp af flatMap funktion på Valgfri vendte tilbage fra getOptional vi kan pakke resultatet af et indre udtryk, der vender tilbage Valgfri. Uden flatMapville resultatet være Valgfri. Det flatMap betjening udføres kun, når Valgfri er ikke tom.

9. Biblioteker

9.1. Brug af Lombok

Lombok er et fantastisk bibliotek, der reducerer mængden af ​​kogepladekode i vores projekter. Den leveres med et sæt annoteringer, der træder i stedet for almindelige kodedele, vi ofte skriver selv i Java-applikationer, såsom getters, setters og toString (), for at nævne et par stykker.

En anden af ​​dens kommentarer er @NonNull. Så hvis et projekt allerede bruger Lombok til at fjerne kedelpladekode, @NonNull kan erstatte behovet for nul kontrol.

Før vi går videre for at se nogle eksempler, lad os tilføje en Maven-afhængighed for Lombok:

 org.projektlombok lombok 1.18.6 

Nu kan vi bruge @NonNull hvor end en nul check er nødvendig:

offentlig ugyldig accept (@NonNull Object param) {System.out.println (param); }

Så vi kommenterede simpelthen det objekt, for hvilket nul check ville have været påkrævet, og Lombok genererer den kompilerede klasse:

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

Hvis param er nul, denne metode kaster en NullPointerException. Metoden skal gøre dette eksplicit i sin kontrakt, og klientkoden skal håndtere undtagelsen.

9.2. Ved brug af StringUtils

Generelt, Snor validering inkluderer en kontrol af en tom værdi ud over nul værdi. Derfor vil en fælles valideringserklæring være:

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

Dette bliver hurtigt overflødigt, hvis vi skal håndtere en masse Snor typer. Det er her StringUtils kommer praktisk. Før vi ser dette i aktion, lad os tilføje en Maven-afhængighed for commons-lang3:

 org.apache.commons commons-lang3 3.8.1 

Lad os nu omformulere ovenstående kode med StringUtils:

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

Så vi erstattede vores nul eller tom kontrol med en statisk hjælpemetode isNotEmpty (). Denne API tilbyder andre effektive værktøjsmetoder til håndtering af almindelige Snor funktioner.

10. Konklusion

I denne artikel kiggede vi på de forskellige grunde til NullPointerException og hvorfor det er svært at identificere. Derefter så vi forskellige måder at undgå redundans i kode omkring kontrol af nul med parametre, returtyper og andre variabler.

Alle eksemplerne er tilgængelige på GitHub.