kerülje a Null utasítás ellenőrzését a Java

áttekintés

általában a null változók, hivatkozások és gyűjtemények kezelése bonyolult a Java kódban. Nem csak nehéz azonosítani őket, de bonyolult is kezelni őket.

ami azt illeti, a nullával kapcsolatos hibák fordításkor nem azonosíthatók, és futásidőben Nullpointerexceptiont eredményeznek.

ebben az oktatóanyagban megnézzük, hogy ellenőrizni kell-e a Null-t a Java-ban és különböző alternatívákat, amelyek segítenek elkerülni a null-ellenőrzéseket a kódunkban.

Further reading:

Using NullAway to Avoid NullPointerExceptions

Learn how to avoid NullPointerExceptions using NullAway.
Read more →

Spring Null-Safety Annotations

A quick and practical guide to null-safety annotations in Spring.
Read more →

Introduction to the Null Object Pattern

Learn about the Null Object Pattern and how to implement it in Java
Read more →

What Is NullPointerException?

a NullPointerException Javadoc szerint akkor dobják el, amikor egy alkalmazás megpróbálja használni a null-t olyan esetben, amikor egy objektumra van szükség, például:

  • egy null objektum példány metódusának meghívása
  • Egy null objektum mezőjének elérése vagy módosítása
  • A null hosszát úgy veszi, mintha tömb lenne
  • A null résidőinek elérése vagy módosítása, mintha tömb lenne
  • A null Résidőinek elérése vagy módosítása
  • ha ez egy dobható érték

nézzük meg gyorsan néhány példát a Java kódra, amely ezt a kivételt okozza:

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

itt megpróbáltunk meghívni egy metódushívást egy null hivatkozásra. Ez NullPointerException-t eredményezne.

egy másik gyakori példa, ha megpróbálunk elérni egy null tömböt:

public static void main(String args) { findMax(null);}private static void findMax(int arr) { int max = arr; //check other elements in loop}

Ez NullPointerException-t okoz a 6.sorban.

így a null objektum bármely mezőjének, metódusának vagy indexének elérése NullPointerException-t okoz, amint az a fenti példákból látható.

a NullPointerException elkerülésének általános módja a null ellenőrzése:

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

a valós világban a programozók nehezen tudják azonosítani, hogy mely objektumok lehetnek nullák. Egy agresszíven biztonságos stratégia lehet, hogy ellenőrizze null minden objektumot. Ez azonban sok redundáns nullellenőrzést okoz, és kevésbé olvashatóvá teszi a kódunkat.

a következő néhány szakaszban áttekintjük a Java néhány alternatíváját, amelyek elkerülik az ilyen redundanciát.

A null kezelése az API-szerződésen keresztül

amint azt az utolsó szakaszban tárgyaltuk, a null objektumok metódusainak vagy változóinak elérése NullPointerException-t okoz. Azt is megvitattuk, hogy egy objektum nullellenőrzése a hozzáférés előtt kiküszöböli a NullPointerException lehetőségét.

gyakran vannak olyan API-k, amelyek képesek kezelni a null értékeket. Például:

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; }}

a print() metódushívás csak” null ” – t nyomtat, de nem dob kivételt. Hasonlóképpen, a process () soha nem tér vissza null válaszában. Inkább kivételt dob.

tehát a fenti API-khoz hozzáférő kliens kódhoz nincs szükség null ellenőrzésre.

az ilyen API-knak azonban ezt kifejezetten meg kell fogalmazniuk a szerződésükben. Az API-k közös helye egy ilyen szerződés közzétételére a JavaDoc.

Ez azonban nem ad egyértelmű utalást az API-szerződésre, ezért az ügyfélkód-fejlesztőkre támaszkodik annak megfelelőségének biztosítása érdekében.

a következő részben meglátjuk, hogy néhány ide és más fejlesztőeszköz hogyan segít ebben a fejlesztőknek.

API szerződések automatizálása

4.1. Statikus kódelemzés használata

a statikus kódelemző eszközök nagyban javítják a kód minőségét. Néhány ilyen eszköz lehetővé teszi a fejlesztők számára a null szerződés fenntartását is. Az egyik példa a FindBugs.

FindBugs segít kezelni a null szerződés révén a @Nullable és @NonNull kommentárok. Ezeket a megjegyzéseket bármilyen módszer, mező, helyi változó vagy paraméter felett használhatjuk. Ez egyértelművé teszi az ügyfélkód számára, hogy a jegyzetelt Típus lehet-e null vagy sem. Lássunk egy példát:

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

itt a @NonNull egyértelművé teszi, hogy az argumentum nem lehet null. Ha az ügyfélkód meghívja ezt a módszert anélkül, hogy ellenőrizné a null argumentumot, a FindBugs figyelmeztetést generál fordításkor.

4.2. Az IDE támogatás használata

a fejlesztők általában az IDEs-re támaszkodnak a Java kód írásakor. És az olyan funkciók, mint az intelligens kód befejezése és a hasznos figyelmeztetések, például amikor egy változót nem rendeltek hozzá, minden bizonnyal nagy mértékben segítenek.

egyes IDE-k lehetővé teszik a fejlesztők számára az API-szerződések kezelését, ezáltal kiküszöbölve a statikus kódelemző eszköz szükségességét. IntelliJ IDEA biztosítja a @NonNull és @ Nullable kommentárok. Ahhoz, hogy az IntelliJ-ben támogatást adjunk ezekhez a megjegyzésekhez, hozzá kell adnunk a következő Maven-függőséget:

<dependency> <groupId>org.jetbrains</groupId> <artifactId>annotations</artifactId> <version>16.0.2</version></dependency>

most az IntelliJ figyelmeztetést generál, ha hiányzik a nullellenőrzés, mint az utolsó példánkban.

az IntelliJ Szerződéses megjegyzést is biztosít a komplex API-szerződések kezelésére.

5. Állítások

eddig csak arról beszéltünk, hogy eltávolítjuk a null ellenőrzések szükségességét az ügyfélkódból. De, ez ritkán alkalmazható a valós alkalmazásokban.

tegyük fel, hogy olyan API-val dolgozunk, amely nem tudja elfogadni a null paramétereket, vagy vissza tud adni egy null választ, amelyet az ügyfélnek kell kezelnie. Ez szükségessé teszi számunkra, hogy ellenőrizzük a paramétereket vagy a választ egy null értékre.

itt használhatunk Java állításokat a hagyományos null check feltételes utasítás helyett:

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

a 2.sorban ellenőrizzük a null paramétert. Ha az állítások engedélyezve vannak, ez Assertionerrort eredményezne.

bár ez jó módszer az előfeltételek, például a Nem null paraméterek érvényesítésére, ennek a megközelítésnek két fő problémája van:

  1. az állítások általában le vannak tiltva egy JVM-ben
  2. a hamis állítás nem ellenőrzött hibát eredményez, amely helyrehozhatatlan

ezért a programozóknak nem ajánlott állításokat használni a feltételek ellenőrzésére. A következő szakaszokban a null validációk kezelésének egyéb módjait tárgyaljuk.

A Null ellenőrzések elkerülése kódolási gyakorlatokon keresztül

6.1. Előfeltételek

általában jó gyakorlat a Korán sikertelen kód írása. Ezért, ha egy API több olyan paramétert fogad el, amelyek nem lehetnek null, akkor jobb, ha az API előfeltételeként minden nem null paramétert ellenőriz.

nézzünk meg például két módszert – az egyik Korán sikertelen, a másik pedig nem:

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); }}

nyilvánvaló, hogy a goodAccept () – t kell előnyben részesítenünk a badAccept () – nél.

alternatívaként a Guava előfeltételeit is használhatjuk az API paraméterek érvényesítéséhez.

6.2. Primitívek használata Wrapper osztályok helyett

mivel a null nem elfogadható érték az olyan primitívek számára, mint az int, ahol csak lehetséges, előnyben kell részesítenünk őket a wrapper társaikkal szemben, mint az egész szám.

tekintsünk egy olyan módszer két implementációját, amely két egész számot összegez:

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

nevezzük ezeket az API-kat az ügyfélkódunkban:

int sum = primitiveSum(null, 2);

Ez fordítási idejű hibát eredményezne, mivel a null nem érvényes érték egy int számára.

és amikor az API-t wrapper osztályokkal használjuk, NullPointerException-t kapunk:

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

vannak más tényezők is a primitívek csomagolópapírokon keresztüli használatához, amint azt egy másik oktatóprogramban, a Java Primitives versus Objects-ben tárgyaltuk.

6, 3. Üres gyűjtemények

alkalmanként vissza kell adnunk egy gyűjteményt egy módszer válaszaként. Az ilyen módszereknél mindig meg kell próbálnunk egy üres gyűjteményt visszaadni a null helyett:

public List<String> names() { if (userExists()) { return Stream.of(readName()).collect(Collectors.toList()); } else { return Collections.emptyList(); }}

ezért elkerültük, hogy ügyfelünknek nullellenőrzést kelljen végrehajtania a módszer meghívásakor.

objektumok használata

a Java 7 bemutatta az új Objects API-t. Ennek az API-nak számos statikus segédprogramja van, amelyek sok redundáns kódot vesznek el. Nézzünk meg egy ilyen módszert, requireNonNull ():

public void accept(Object param) { Objects.requireNonNull(param); // doSomething()}

most teszteljük az accept() módszert:

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

tehát, ha a null argumentumként kerül átadásra, az accept () NullPointerException-t dob.

Ez az osztály isNull() és nonNull() metódusokat is tartalmaz, amelyek predikátumként használhatók egy objektum null értékének ellenőrzésére.

választható

8.1. Használata orElseThrow

Java 8 bevezetett egy új opcionális API a nyelvet. Ez jobb szerződést kínál az opcionális értékek kezelésére a null – hoz képest. Lássuk, hogy az opcionális hogyan szünteti meg a null ellenőrzések szükségességét:

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; }}

Az opcionális visszatérésével, amint azt a fentiekben bemutattuk, a folyamat módszer egyértelművé teszi a hívó számára, hogy a válasz üres lehet, és fordításkor kell kezelni.

Ez különösen elveszi az ügyfélkódban lévő null-ellenőrzések szükségességét. Az üres válasz másképp kezelhető Az opcionális API deklaratív stílusával:

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

ezenkívül jobb szerződést biztosít az API-fejlesztők számára, hogy jelezzék az ügyfeleknek, hogy egy API üres választ adhat vissza.

bár megszüntettük az API hívójának null-ellenőrzésének szükségességét, egy üres válasz visszaadására használtuk. Ennek elkerülése érdekében az opcionális olyan ofNullable metódust ad vissza, amely egy opcionális értéket ad vissza a megadott értékkel, vagy üres, ha az érték null:

public Optional<Object> process(boolean processed) { String response = doSomething(processed); return Optional.ofNullable(response);}

8.2. Használata opcionális gyűjtemények

míg foglalkozó üres gyűjtemények, opcionális jól jön:

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

Ez a függvény a lista első elemét adja vissza. A Stream API findFirst funkciója üres választékot ad vissza, ha nincs adat. Itt az orElse-t használtuk alapértelmezett érték megadására.

Ez lehetővé teszi számunkra, hogy üres listákat vagy listákat kezeljünk, amelyek a Stream library szűrési módszerének használata után nem tartalmaznak elemeket.

Alternatív megoldásként engedélyezhetjük az ügyfél számára, hogy eldöntse, hogyan kell kezelni az ürességet, ha ebből a módszerből választhatóan visszatér:

public Optional<String> findOptionalFirst() { return getList().stream() .findFirst();}

ezért, ha a getList eredménye üres, ez a módszer egy üres választékot ad vissza az ügyfélnek.

Az opcionális gyűjtemények használata lehetővé teszi számunkra, hogy olyan API-kat tervezzünk, amelyek biztosan nem null értékeket adnak vissza, elkerülve ezzel az ügyfél explicit null ellenőrzését.

itt fontos megjegyezni, hogy ez a megvalósítás támaszkodik getList nem tér vissza null. Amint azonban az utolsó szakaszban tárgyaltuk, gyakran jobb, ha üres listát ad vissza, nem pedig nullát.

8, 3. Kombinálása Optionals

amikor elkezdjük, hogy a funkciók vissza opcionális szükségünk van egy módja annak, hogy összekapcsolják az eredményeket egyetlen értéket. Vegyük a getlist korábbi példáját. Mi lenne, ha Visszaadna egy opcionális listát, vagy olyan módszerrel kellene becsomagolni, amely null-t csomagol opcionális használatával ofNullable?

a findFirst metódusunk egy opcionális lista első elemét akarja visszaadni:

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

a Getoptional-ból visszaadott opcionális Flatmap függvény használatával kicsomagolhatjuk az opcionális belső kifejezés eredményét. FlatMap nélkül az eredmény opcionális lenne<opcionális<String>>. A flatMap művelet csak akkor kerül végrehajtásra, ha az opcionális nem üres.

könyvtárak

9.1. A Lombok használata

A Lombok egy nagyszerű könyvtár, amely csökkenti a boilerplate kód mennyiségét projektjeinkben. Jön egy sor kommentárok, hogy vegye át a helyét a közös részei kód gyakran írjuk magunkat a Java alkalmazások, mint például getters, setters, és toString (), hogy csak néhányat említsünk.

egy másik kommentárja a @NonNull. Tehát, ha egy projekt már használja a Lombok-ot a boilerplate kód kiküszöbölésére, akkor a @NonNull helyettesítheti a null ellenőrzések szükségességét.

mielőtt néhány példát látnánk, adjunk hozzá egy Maven függőséget a Lombokhoz:

<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.6</version></dependency>

most már használhatjuk a @NonNull-t, ahol null-ellenőrzésre van szükség:

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

tehát egyszerűen megjegyeztük azt az objektumot, amelyhez a null-ellenőrzés szükséges lett volna, és generálja a lefordított osztályt:

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

Ha a param értéke NULL, ez a módszer NullPointerException-t dob. A metódusnak ezt kifejezetten meg kell fogalmaznia a szerződésében, az ügyfélkódnak pedig kezelnie kell a kivételt.

9, 2. Használata StringUtils

általában a karakterlánc-érvényesítés a null érték mellett egy üres érték ellenőrzését is tartalmazza. Ezért egy általános érvényesítési utasítás a következő lenne:

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

Ez gyorsan feleslegessé válik, ha sok Karakterlánctípussal kell foglalkoznunk. Itt jön jól a StringUtils. Mielőtt ezt működés közben látnánk, adjunk hozzá egy Maven függőséget a commons-lang3-hoz:

<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.11</version></dependency>

most tegyük újra a fenti kódot a StringUtils segítségével:

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

tehát a null vagy üres csekket egy statikus isnotempty () segédprogrammal helyettesítettük. Ez az API más hatékony segédprogram módszereket kínál a közös karakterlánc-funkciók kezelésére.

következtetés

ebben a cikkben megvizsgáltuk a NullPointerException különböző okait, és miért nehéz azonosítani. Ezután különféle módszereket láttunk a kód redundanciájának elkerülésére A null ellenőrzése körül paraméterekkel, visszatérési típusokkal és más változókkal.

az összes példa elérhető a Githubon.

kezdje el a tavaszi 5 és a tavaszi Boot 2 használatát a tavaszi tanfolyamon keresztül:

>> nézze meg a tanfolyamot



Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.