Vermijd Check for Null-instructie in Java

1. Overzicht

Over het algemeen, nul variabelen, verwijzingen en verzamelingen zijn lastig te verwerken in Java-code. Ze zijn niet alleen moeilijk te identificeren, maar ze zijn ook complex om mee om te gaan.

In feite, elke misser in het omgaan met nul kan niet worden geïdentificeerd tijdens het compileren en resulteert in een NullPointerException tijdens runtime.

In deze zelfstudie bekijken we de noodzaak om te controleren op nul in Java en verschillende alternatieven die ons helpen om te vermijden nul checkt in onze code.

2. Wat is NullPointerException?

Volgens de Javadoc voor NullPointerException, het wordt gegenereerd wanneer een toepassing probeert te gebruiken nul in een geval waarin een object vereist is, zoals:

  • Een instantiemethode aanroepen van een nul voorwerp
  • Toegang tot of wijziging van een veld van een nul voorwerp
  • De lengte van nul alsof het een array is
  • Toegang krijgen tot of wijzigen van de slots van nul alsof het een array is
  • Gooien nul alsof het een Gooibaar waarde

Laten we snel een paar voorbeelden bekijken van de Java-code die deze uitzondering veroorzaakt:

public void doSomething () {String resultaat = doSomethingElse (); if (result.equalsIgnoreCase ("Success")) // success}} private String doSomethingElse () {return null; }

Hier, we hebben geprobeerd een methodeaanroep aan te roepen voor een nul referentie. Dit zou resulteren in een NullPointerException.

Een ander veelvoorkomend voorbeeld is als we proberen toegang te krijgen tot een nul matrix:

openbare statische leegte hoofd (String [] args) {findMax (null); } privé statische leegte findMax (int [] arr) {int max = arr [0]; // controleer andere elementen in loop}

Dit veroorzaakt een NullPointerException op regel 6.

Dus toegang tot elk veld, methode of index van een nul object veroorzaakt een NullPointerException, zoals blijkt uit de bovenstaande voorbeelden.

Een veel voorkomende manier om de NullPointerException is om te controleren nul:

public void doSomething () {String resultaat = doSomethingElse (); if (result! = null && result.equalsIgnoreCase ("Success")) {// succes} else // failure} private String doSomethingElse () {return null; }

In de echte wereld vinden programmeurs het moeilijk om te identificeren welke objecten kunnen zijn nul. Een agressief veilige strategie zou kunnen zijn om te controleren nul voor elk object. Dit zorgt echter voor veel overtolligheid nul controleert en maakt onze code minder leesbaar.

In de volgende secties zullen we enkele alternatieven in Java bespreken die dergelijke redundantie vermijden.

3. Behandeling nul Via het API-contract

Zoals besproken in de laatste sectie, toegang tot methoden of variabelen van nul objecten veroorzaakt een NullPointerException. We hebben ook besproken dat het zetten van een nul het controleren van een object voordat u het opent, elimineert de mogelijkheid van NullPointerException.

Er zijn echter vaak API's die ermee overweg kunnen nul waarden. Bijvoorbeeld:

public void print (Object param) {System.out.println ("Printing" + param); } public Object process () gooit Uitzondering {Object resultaat = doSomething (); if (result == null) {throw new Exception ("Verwerking mislukt. Kreeg een null-antwoord"); } else {resultaat retourneren; }}

De afdrukken() method call zou gewoon afdrukken "nul" maar zal geen uitzondering werpen. Evenzo werkwijze() zou nooit meer terugkeren nul in zijn antwoord. Het gooit eerder een Uitzondering.

Dus voor een klantcode die toegang heeft tot de bovenstaande API's, is er geen nul controleren.

Dergelijke API's moeten dit echter expliciet maken in hun contract. Een veelgebruikte plaats voor API's om een ​​dergelijk contract te publiceren, is de JavaDoc.

Dit geeft echter geen duidelijke indicatie van het API-contract en vertrouwt daarom op de ontwikkelaars van de klantcode om de naleving ervan te waarborgen.

In de volgende sectie zullen we zien hoe een paar IDE's en andere ontwikkeltools ontwikkelaars hierbij helpen.

4. Automatisering van API-contracten

4.1. Statische code-analyse gebruiken

Hulpprogramma's voor het analyseren van statische codes helpen de codekwaliteit aanzienlijk te verbeteren. En een paar van dergelijke tools stellen de ontwikkelaars ook in staat om het nul contract. Een voorbeeld is FindBugs.

FindBugs helpt bij het beheren van het nul contract via de @Nullable en @RTLnieuws annotaties. We kunnen deze annotaties gebruiken voor elke methode, veld, lokale variabele of parameter. Dit maakt het expliciet voor de klantcode of het geannoteerde type kan zijn nul of niet. Laten we een voorbeeld bekijken:

openbare ongeldige acceptatie (@Nonnull Objectparameter) {System.out.println (param.toString ()); }

Hier, @RTLnieuws maakt duidelijk dat het argument niet kan zijn nul. Als de clientcode deze methode aanroept zonder het argument voor nul, FindBugs zou tijdens het compileren een waarschuwing genereren.

4.2. IDE-ondersteuning gebruiken

Ontwikkelaars vertrouwen over het algemeen op IDE's voor het schrijven van Java-code. En functies zoals het aanvullen van slimme codes en nuttige waarschuwingen, zoals wanneer een variabele misschien niet is toegewezen, helpen zeker in grote mate.

Sommige IDE's stellen ontwikkelaars ook in staat API-contracten te beheren en daardoor de behoefte aan een statische code-analysetool te elimineren. IntelliJ IDEA biedt de @RTLnieuws en @Nullable annotaties. Om de ondersteuning voor deze annotaties in IntelliJ toe te voegen, moeten we de volgende Maven-afhankelijkheid toevoegen:

 org.jetbrains annotaties 16.0.2 

Nu, IntelliJ genereert een waarschuwing als het nul vinkje ontbreekt, zoals in ons laatste voorbeeld.

IntelliJ biedt ook een Contract annotatie voor het afhandelen van complexe API-contracten.

5. Beweringen

Tot nu toe hebben we het alleen gehad over het verwijderen van de noodzaak voor nul controles van de klantcode. Maar dat is zelden van toepassing in real-world applicaties.

Laten we nu stel dat we werken met een API die niet kan accepteren nul parameters of kan een nul reactie die door de klant moet worden afgehandeld. Dit geeft ons de noodzaak om de parameters of het antwoord op een nul waarde.

Hier kunnen we Java-beweringen gebruiken in plaats van de traditionele nul controleer voorwaardelijke verklaring:

openbare ongeldige acceptatie (Objectparameter) {assert param! = null; doSomething (param); }

In regel 2 controleren we op een nul parameter. Als de beweringen zijn ingeschakeld, zou dit resulteren in een AssertionFout.

Hoewel het een goede manier is om voorwaarden te stellen, zoalsnul parameters, deze benadering heeft twee grote problemen:

  1. Beweringen zijn meestal uitgeschakeld in een JVM
  2. EEN false bewering resulteert in een niet-gecontroleerde fout die niet kan worden hersteld

Daarom wordt het voor programmeurs niet aanbevolen om Assertions te gebruiken om condities te controleren. In de volgende secties bespreken we andere manieren van omgaan nul validaties.

6. Vermijden Nul Controleert door middel van coderingspraktijken

6.1. Randvoorwaarden

Het is meestal een goede gewoonte om code te schrijven die vroegtijdig mislukt. Daarom, als een API meerdere parameters accepteert die niet zijn toegestaan nul, het is beter om elke niet-nul parameter als voorwaarde voor de API.

Laten we bijvoorbeeld eens kijken naar twee methoden: een die vroeg mislukt en een die dat niet doet:

public void goodAccept (String one, String two, String three) {if (one == null || two == null || three == null) {throw new IllegalArgumentException (); } proces (één); proces (twee); proces (drie); } public void badAccept (String één, String twee, String drie) {if (one == null) {throw nieuwe IllegalArgumentException (); } else {proces (één); } if (two == null) {gooi nieuwe IllegalArgumentException (); } else {proces (twee); } if (three == null) {gooi nieuwe IllegalArgumentException (); } else {proces (drie); }}

Het is duidelijk dat we de voorkeur moeten geven goed accepteren () over- badAccept ().

Als alternatief kunnen we ook de voorwaarden van Guava gebruiken voor het valideren van API-parameters.

6.2. Primitieven gebruiken in plaats van wrapper-klassen

Sinds nul is geen acceptabele waarde voor primitieven zoals int, we zouden ze verkiezen boven hun tegenhangers in de verpakking, zoals Geheel getal waar mogelijk.

Beschouw twee implementaties van een methode die twee gehele getallen optelt:

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

Laten we nu deze API's aanroepen in onze klantcode:

int sum = primitiveSum (null, 2);

Dit zou resulteren in een compilatietijdfout sinds nul is geen geldige waarde voor een int.

En als we de API met wrapper-klassen gebruiken, krijgen we een NullPointerException:

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

Er zijn ook andere factoren voor het gebruik van primitieven over wrappers, zoals we hebben besproken in een andere tutorial, Java Primitives versus Objects.

6.3. Lege collecties

Af en toe moeten we een collectie retourneren als reactie van een methode. Voor dergelijke methoden moeten we altijd proberen retourneer een lege collectie in plaats van nul:

openbare lijstnamen () {if (userExists ()) {return Stream.of (readName ()). collect (Collectors.toList ()); } else {return Collections.emptyList (); }}

Daarom hebben we voorkomen dat onze klant een nul controleer bij het aanroepen van deze methode.

7. Met behulp van Voorwerpen

Java 7 introduceerde het nieuwe Voorwerpen API. Deze API heeft verschillende statisch hulpprogramma-methoden die veel overtollige code wegnemen. Laten we eens kijken naar zo'n methode, vereisenNonNull ():

openbare leegte accepteren (Objectparameter) {Objects.requireNonNull (param); // doe iets() }

Laten we nu de aanvaarden() methode:

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

Dus als nul wordt doorgegeven als argument, aanvaarden() gooit een NullPointerException.

Deze klasse heeft ook is niets() en nonNull () methoden die kunnen worden gebruikt als predikaten om een ​​object op te controleren nul.

8. Met behulp van Optioneel

8.1. Gebruik makend van orElseThrow

Java 8 introduceerde een nieuw Optioneel API in de taal. Dit biedt een beter contract voor het afhandelen van optionele waarden in vergelijking met nul. Laten we eens kijken hoe Optioneel neemt de behoefte aan nul controleert:

openbaar Optioneel proces (boolean verwerkt) {String response = doSomething (verwerkt); if (response == null) {return Optional.empty (); } return Optioneel.of (antwoord); } private String doSomething (boolean verwerkt) {if (verwerkt) {retourneer "geslaagd"; } else {retourneer null; }}

Door een Optioneel, zoals hierboven getoond, de werkwijze methode maakt het de beller duidelijk dat het antwoord leeg kan zijn en tijdens het compileren moet worden afgehandeld.

Dit neemt met name de behoefte aan iets weg nul controleert de klantcode. Een leeg antwoord kan anders worden afgehandeld met behulp van de declaratieve stijl van de Optioneel API:

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

Bovendien biedt het ook een beter contract met API-ontwikkelaars om aan de klanten aan te geven dat een API een leeg antwoord kan retourneren.

Hoewel we de noodzaak van een nul controleer de aanroeper van deze API, we hebben deze gebruikt om een ​​leeg antwoord te retourneren. Om dit te vermijden, Optioneel biedt een ofNullable methode die een Optioneel met de opgegeven waarde, of leeg, als de waarde is nul:

openbaar Optioneel proces (boolean verwerkt) {String response = doSomething (verwerkt); return Optioneel.ofNullable (antwoord); }

8.2. Gebruik makend van Optioneel met collecties

Bij het omgaan met lege collecties, Optioneel komt goed van pas:

openbare String findFirst () {retour getList (). stream () .findFirst () .orElse (DEFAULT_VALUE); }

Deze functie moet het eerste item van een lijst retourneren. De Stroom API's findFirst functie retourneert een lege Optioneel als er geen gegevens zijn. Hier hebben we gebruikt of anders om in plaats daarvan een standaardwaarde op te geven.

Dit stelt ons in staat om ofwel lege lijsten ofwel lijsten te behandelen, die nadat we de Stroom bibliotheek filter methode, hebben geen items om te leveren.

Als alternatief kunnen we de klant ook laten beslissen hoe hij ermee om moet gaan leeg door terug te keren Optioneel van deze methode:

openbaar Optioneel findOptionalFirst () {return getList (). stream () .findFirst (); }

Daarom, als het resultaat van getList leeg is, retourneert deze methode een lege Optioneel naar de klant.

Gebruik makend van Optioneel with Collections stelt ons in staat API's te ontwerpen die zeker niet-null-waarden retourneren, waardoor expliciete nul controleert de cliënt.

Het is belangrijk op te merken dat deze implementatie afhankelijk is van getList niet terugkeren nul. Zoals we in de vorige sectie hebben besproken, is het echter vaak beter om een ​​lege lijst te retourneren in plaats van een nul.

8.3. Optionals combineren

Wanneer we beginnen onze functies terug te laten keren Optioneel we hebben een manier nodig om hun resultaten in één enkele waarde te combineren. Laten we onze getList voorbeeld uit eerder. Wat als het een Optioneel lijst, of zouden worden verpakt met een methode die een nul met Optioneel gebruik makend van ofNullable?

Onze findFirst methode wil een Optioneel eerste element van een Optioneel lijst:

openbaar Optioneel optionalListFirst () {retour getOptionalList () .flatMap (lijst -> lijst.stream (). findFirst ()); }

Door de flatMap functie op de Optioneel teruggekomen van getOptional we kunnen het resultaat uitpakken van een innerlijke uitdrukking die terugkeert Optioneel. Zonder flatMap, het resultaat zou zijn Optioneel. De flatMap bewerking wordt alleen uitgevoerd als de Optioneel is niet leeg.

9. Bibliotheken

9.1. Met behulp van Lombok

Lombok is een geweldige bibliotheek die de hoeveelheid standaardcode in onze projecten vermindert. Het wordt geleverd met een reeks annotaties die de plaats innemen van veelvoorkomende delen van de code die we zelf vaak schrijven in Java-toepassingen, zoals getters, setters en toString (), om er een paar te noemen.

Een andere annotatie is @RTLnieuws Dus als een project al Lombok gebruikt om standaardcode te elimineren, @RTLnieuws kan de behoefte aan nul cheques.

Voordat we verder gaan met enkele voorbeelden, laten we een Maven-afhankelijkheid voor Lombok toevoegen:

 org.projectlombok lombok 1.18.6 

Nu kunnen we gebruiken @RTLnieuws waar een nul controle is nodig:

openbare ongeldige acceptatie (@NonNull Objectparameter) {System.out.println (param); }

We hebben dus eenvoudigweg het object geannoteerd waarvoor de nul check zou vereist zijn geweest, en Lombok genereert de gecompileerde klasse:

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

Als param is nul, deze methode gooit een NullPointerException. De methode moet dit expliciet maken in zijn contract, en de clientcode moet de uitzondering afhandelen.

9.2. Gebruik makend van StringUtils

Over het algemeen, Draad validatie omvat een controle op een lege waarde naast nul waarde. Daarom zou een algemene validatieverklaring zijn:

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

Dit wordt al snel overbodig als we er veel mee te maken hebben Draad types. Dit is waar StringUtils is handig. Voordat we dit in actie zien, laten we een Maven-afhankelijkheid toevoegen voor commons-lang3:

 org.apache.commons commons-lang3 3.8.1 

Laten we nu de bovenstaande code refactoren met StringUtils:

openbare ongeldige acceptatie (String param) {if (StringUtils.isNotEmpty (param)) System.out.println (param); }

Dus hebben we onze vervangen nul of leeg het vinkje met een statisch hulpprogramma methode is niet leeg(). Deze API biedt andere krachtige hulpprogramma's voor het afhandelen van veelgebruikte Draad functies.

10. Conclusie

In dit artikel hebben we gekeken naar de verschillende redenen voor NullPointerException en waarom het moeilijk te identificeren is. Vervolgens hebben we verschillende manieren gezien om de overtollige code rond het controleren op nul met parameters, retourtypen en andere variabelen.

Alle voorbeelden zijn beschikbaar op GitHub.