Kerülje a Null Statement keresését a Java-ban

1. Áttekintés

Általában, nulla változókat, hivatkozásokat és gyűjteményeket bonyolult kezelni Java-kódban. Nemcsak nehéz azonosítani őket, de bonyolult is kezelni őket.

Ami azt illeti, minden hiányosság a foglalkozásban nulla az összeállítás időpontjában nem azonosítható, és a NullPointerException futás közben.

Ebben az oktatóanyagban megvizsgáljuk a keresés szükségességét nulla Java-ban és különféle alternatívák, amelyek segítenek elkerülni nulla ellenőrzi a kódunkat.

2. Mi az NullPointerException?

A Javadoc szerint NullPointerException, akkor dobja el, amikor egy alkalmazás megpróbálja használni nulla abban az esetben, ha objektumra van szükség, például:

  • A. Példány metódusának meghívása nulla tárgy
  • A. Mezőjének elérése vagy módosítása nulla tárgy
  • A hossza nulla mintha tömb lenne
  • Hozzáférés vagy módosítás a nulla mintha tömb lenne
  • Dobás nulla mintha a Dobható érték

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

public void doSomething () {String eredmény = doSomethingElse (); if (result.equalsIgnoreCase ("Siker")) // siker}} private String doSomethingElse () {return null; }

Itt, megpróbáltunk metódust hívni a nulla referencia. Ennek eredményeként a NullPointerException.

Egy másik gyakori példa, ha megpróbálunk hozzáférni a nulla sor:

public static void main (String [] args) {findMax (null); } privát statikus void findMax (int [] arr) {int max = arr [0]; // a hurokban lévő többi elem ellenőrzése}

Ez okozza a NullPointerException a 6. sorban.

Így az a bármely mezőjéhez, módszeréhez vagy indexéhez való hozzáférés nulla objektum okozza a NullPointerException, amint az a fenti példákból kitűnik.

A. Elkerülésének általános módja NullPointerException az, hogy ellenőrizze nulla:

public void doSomething () {String eredmény = doSomethingElse (); if (eredmény! = null && result.equalsIgnoreCase ("Siker")) {// siker} else // hiba} private String doSomethingElse () {return null; }

A való világban a programozók nehezen tudják azonosítani, hogy mely objektumok lehetnek nulla. Agresszív módon biztonságos stratégia lehet az ellenőrzés nulla minden tárgyra. Ez azonban sok felesleget okoz nulla ellenőrzi és kevésbé olvashatóvá teszi kódunkat.

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

3. Kezelés nulla Az API-szerződés révén

Az utolsó szakaszban leírtak szerint hozzáférési módszerek vagy változók nulla tárgyak okozzák a NullPointerException. Megbeszéltük azt is, hogy a nulla egy objektum ellenőrzése, mielőtt hozzáférne hozzá, kizárja annak lehetőségét NullPointerException.

Azonban gyakran vannak olyan API-k, amelyek képesek kezelni nulla értékek. Például:

public void print (Object param) {System.out.println ("Nyomtatás" + param); } public Object process () dobja a Kivételt {Object result = doSomething (); if (eredmény == null) {dobjon új kivételet ("A feldolgozás sikertelen. Null választ kapott"); } else {visszatérési eredmény; }}

A nyomtatás() a módszer hívása csak kinyomtatna "nulla" de nem vet ki kivételt. Hasonlóképpen, folyamat() soha nem térne vissza nulla válaszában. Inkább dob egy Kivétel.

Tehát a fenti API-khoz hozzáférő klienskódhoz nincs szükség a nulla jelölje be.

Az ilyen API-knak azonban a szerződésükben egyértelművé kell tenniük. Az API-k számára egy ilyen szerződés közzétételének általános helye a JavaDoc.

Ez azonban ad nincs egyértelmű jelzés az API-szerződésről, és ezért az ügyfélkód-fejlesztőkre támaszkodik annak biztosítása érdekében.

A következő szakaszban megnézzük, hogy néhány IDE és más fejlesztői eszközök hogyan segítik a fejlesztőket ebben.

4. API-szerződések automatizálása

4.1. A statikus kódelemzés használata

A statikus kódelemző eszközök sokat segítenek a kódminőség javításában. Néhány ilyen eszköz lehetővé teszi a fejlesztők számára a nulla szerződés. Ilyen például a FindBugs.

A FindBugs segít a nulla szerződés révén @Nullable és @NonNull annotációk. 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 lehet-e az annotált típus nulla vagy nem. Lássunk egy példát:

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

Itt, @NonNull világossá teszi, hogy az érv nem lehet nulla. Ha az ügyfélkód meghívja ezt a módszert anélkül, hogy ellenőrizné a argumentumot nulla, A FindBugs figyelmeztetést generálna a fordítás idején.

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

A fejlesztők általában az IDE-kre támaszkodnak a Java-kód írásakor. És az olyan funkciók, mint az intelligens kódfeltöltés és a hasznos figyelmeztetések, például amikor egy változót nem rendeltek hozzá, bizonyosan 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, és ezáltal kiküszöbölik a statikus kódelemző eszköz szükségességét. Az IntelliJ IDEA biztosítja @NonNull és @Nullable annotációk. Ezen megjegyzések támogatásának hozzáadásához az IntelliJ alkalmazásban hozzá kell adnunk a következő Maven-függőséget:

 org.jetbrains annotációk 16.0.2 

Most, Az IntelliJ figyelmeztetést generál, ha a nulla hiányzik az ellenőrzés, mint az utolsó példánkban.

Az IntelliJ a Szerződés kommentár az összetett API-szerződések kezeléséhez.

5. Állítások

Eddig csak a szükséglet megszüntetéséről beszéltünk nulla ellenőrzi az ügyfél kódját. De ez ritkán alkalmazható a valós alkalmazásokban.

Nézzük tegyük fel, hogy olyan API-val dolgozunk, amely nem képes elfogadni nulla paramétereket, vagy visszatérhet a nulla választ, amelyet az ügyfélnek kell kezelnie. Ez azt mutatja, hogy ellenőriznünk kell az a paramétereit vagy válaszát nulla érték.

Itt használhatunk Java Assertions-t a hagyományos helyett nulla feltételes nyilatkozat ellenőrzése:

public void accept (Object param) {állítás param! = null; doSomething (param); }

A 2. sorban ellenőrizzük a nulla paraméter. Ha az állítások engedélyezve vannak, akkor ez egy AssertionError.

Bár ez egy jó módszer az olyan előfeltételek érvényesítésére, mint a nemnulla paraméterek, ennek a megközelítésnek két fő problémája van:

  1. Az állításokat általában egy JVM-ben tiltják le
  2. A hamis az állítás ellenőrizetlen hibát eredményez, amely helyrehozhatatlan

Ezért a programozóknak nem ajánlott az Asertions használatát a feltételek ellenőrzéséhez. A következő szakaszokban a kezelés egyéb módjait tárgyaljuk nulla érvényesítések.

6. Elkerülése Nulla Ellenőrzések a kódolási gyakorlatok révén

6.1. Előfeltételek

Általában jó gyakorlat, ha olyan kódot írunk, amely korán elbukik. Ezért, ha az API több olyan paramétert is elfogad, amelyek nem megengedettek nulla, jobb, ha minden nemnulla paraméter az API előfeltétele.

Nézzünk meg például két módszert - egyet, amely korán kudarcot vall, és nem:

public void goodAccept (első karakterlánc, második karakterlánc, három karakterlánc) {if (one == null || kettő == null || három == null) {dobjon új IllegalArgumentException (); } folyamat (egy); folyamat (kettő); folyamat (három); } public void badAccept (első karakterlánc, második karakterlánc, három karakterlánc) {if (one == null) {dobjon új IllegalArgumentException (); } else {process (one); } if (kettő == null) {dobjon új IllegalArgumentException (); } else {folyamat (kettő); } if (három == null) {dobjon új IllegalArgumentException (); } else {folyamat (három); }}

Nyilvánvaló, hogy előnyben kellene részesítenünk goodAccept () felett badAccept ().

Alternatív megoldásként használhatjuk a guavai előfeltételeket az API paraméterek érvényesítéséhez is.

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

Mivel nulla nem elfogadható érték az olyan primitívek számára, mint int, előnyben kell részesítenünk őket, mint a burkoló társaik, mint a Egész szám ahol csak lehet.

Vegyünk egy módszer két megvalósítását, amely két egész számot összegez:

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

Most hívjuk meg ezeket az API-kat az ügyfélkódban:

int összeg = primitívSum (null, 2);

Ez fordítási időbeli hibát eredményezne nulla nem érvényes értéke egy int.

Ha az API-t burkoló osztályokkal használjuk, akkor a NullPointerException:

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

Más tényezők is vannak a primitívek használatára a burkolók felett, amint azt egy másik oktatóanyagban, a Java primitívek és objektumok verseiben ismertettük.

6.3. Üres gyűjtemények

Esetenként vissza kell adnunk egy gyűjteményt válaszként egy módszerből. Az ilyen módszereknél mindig meg kell próbálnunk egy üres gyűjteményt adjon vissza nulla:

public List nevek () {if (userExists ()) {return Stream.of (readName ()). gyűjt (Collectors.toList ()); } else {return Collections.emptyList (); }}

Ezért elkerültük, hogy ügyfelünk elvégezze a nulla jelölje be ezt a módszert.

7. Használata Tárgyak

A Java 7 bemutatta az újat Tárgyak API. Ennek az API-nak több is van statikus segédprogramok, amelyek sok redundáns kódot vesznek el. Nézzünk meg egy ilyen módszert, igényelNonNull ():

public void accept (Object param) {Objects.requireNonNull (param); // csinálj valamit() }

Most teszteljük a elfogad() módszer:

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

Tehát, ha nulla érvként kerül átadásra, elfogad() dob egy NullPointerException.

Ennek az osztálynak is van nulla() és nonNull () módszerek, amelyek predikátumként használhatók egy objektum ellenőrzésére nulla.

8. Használata Választható

8.1. Használata vagyElseThrow

A Java 8 bemutatott egy újat Választható API a nyelven. Ez jobb szerződést kínál az opcionális értékek kezeléséhez a nulla. Lássuk, hogyan Választható elveszi annak szükségességét nulla ellenőrzések:

public Opcionális folyamat (logikai feldolgozás) {String response = doSomething (feldolgozott); if (válasz == null) {return Opcionális.empty (); } return Opcionális.of (válasz); } private String doSomething (logikai feldolgozás) {if (feldolgozott) {return "átment"; } else {return null; }}

Annak visszaadásával Választható, a fentiek szerint, a folyamat módszer egyértelművé teszi a hívó számára, hogy a válasz lehet üres, és fordításkor kell kezelni.

Ez különösen elveszi bármelyik szükségességét nulla ellenőrzi az ügyfélkódot. Az üres választ másképp lehet kezelni a Választható API:

assertThrows (Kivétel.osztály, () -> folyamat (hamis). vagyElseThrow (() -> új kivétel ()));

Továbbá azt is biztosítja jobb szerződés az API-fejlesztőkkel, hogy jelezze az ügyfeleknek, hogy az API üres választ adhat vissza.

Bár feleslegessé vált a nulla ellenőrizze az API hívóját, arra használtuk, hogy üres választ adjon vissza. Ennek elkerülése érdekében Választható biztosít egy ofNullable metódus, amely egy Választható a megadott értékkel, vagy üres, ha az érték nulla:

public Opcionális folyamat (logikai feldolgozás) {String response = doSomething (feldolgozott); return Optional.ofNullable (válasz); }

8.2. Használata Választható gyűjteményekkel

Üres gyűjteményekkel foglalkozva Választható jól jön:

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

Ez a függvény állítólag a lista első elemét adja vissza. A Folyam API-k findFirst funkció üreset ad vissza Választható amikor nincs adat. Itt használtuk különben hogy helyette alapértelmezett értéket adjon meg.

Ez lehetővé teszi számunkra, hogy üres listákat vagy listákat kezeljünk, amelyek a Folyam könyvtár szűrő módszerrel, nincsenek ellátandó tételei.

Alternatív megoldásként azt is lehetővé tehetjük az ügyfél számára, hogy eldöntse, hogyan kezelje üres visszatérve Választható ebből a módszerből:

public Opcionális findOptionalFirst () {return getList (). stream () .findFirst (); }

Ezért, ha az eredménye getList üres, ez a módszer üreset ad vissza Választható az ügyfélnek.

Használata Választható A gyűjteményekkel lehetővé teszi olyan API-k tervezését, amelyek biztosan nem null értékeket adnak vissza, elkerülve ezzel az expliciteket nulla ellenőrzi az ügyfelet.

Fontos itt megjegyezni, hogy ez a megvalósítás támaszkodik getList nem tér vissza nulla. Azonban, amint azt az utolsó szakaszban tárgyaltuk, gyakran jobb, ha egy üres listát adunk vissza a helyett nulla.

8.3. Opcionális lehetőségek kombinálása

Amikor elkezdjük a funkcióink visszatérését Választható szükségünk van arra, hogy eredményeiket egyetlen értékbe egyesítsük. Vegyük a mi getList korábbi példa. Mi lenne, ha visszaadna egy Választható listát, vagy olyan módszerrel kellett csomagolni, amely a nulla val vel Választható felhasználásával ofNullable?

A mi findFirst metódus vissza akar adni egy Választható első eleme egy Választható lista:

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

A flatMap funkció a Választható visszatért getOptional kicsomagolhatjuk a visszatérő belső kifejezés eredményét Választható. Nélkül flatMap, az eredmény az lenne Választható. A flatMap a műveletet csak akkor hajtják végre, ha a Választható nem üres.

9. Könyvtárak

9.1. Lombok használata

A Lombok egy nagyszerű könyvtár, amely csökkenti a kazán kód kódját a projektjeinkben. Jellemző kommentárokkal érkezik, amelyek a kód közös részeinek helyét foglalják magukban, amelyeket gyakran magunk írunk Java alkalmazásokba, például getterek, beállítók és toString (), hogy csak néhányat említsünk.

A másik annotációja az @NonNull. Tehát, ha egy projekt már a Lombokot használja a kazánlap kódjának kiküszöbölésére, @NonNull helyettesítheti az igényt nulla ellenőrzések.

Mielőtt továbblépnénk néhány példához, adjunk hozzá egy Maven-függőséget Lombokhoz:

 org.projectlombok lombok 1.18.6 

Most már használhatjuk @NonNull bárhol a nulla ellenőrzés szükséges:

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

Tehát egyszerűen feljegyeztük azt az objektumot, amelyre a nulla ellenőrzésre lett volna szükség, és a Lombok létrehozza az összeállított osztályt:

public void accept (@NonNull Object param) {if (param == null) {dobjon új NullPointerException-t ("param"); } else {System.out.println (param); }}

Ha param van nulla, ez a módszer dob a NullPointerException. A metódusnak ezt egyértelművé kell tennie a szerződésében, és az ügyfél kódjának kell kezelnie a kivételt.

9.2. Használata StringUtils

Általában, Húr az érvényesítés egy üres érték ellenőrzését is tartalmazza nulla érték. Ezért egy közös é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 sokakkal kell megküzdenünk Húr típusok. Ez az, ahol StringUtils jól jön. Mielőtt ezt működésben látnánk, adjunk hozzá egy Maven-függőséget a commons-lang3-hoz:

 org.apache.commons commons-lang3 3.8.1 

Tegyük most át a fenti kódot a StringUtils:

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

Tehát kicseréltük a sajátunkat nulla vagy üres csekk a statikus hasznossági módszer isNotEmpty (). Ez az API más hatékony segédprogramokat kínál a közös kezeléséhez Húr funkciókat.

10. Következtetés

Ebben a cikkben megvizsgáltuk a különféle okokat NullPointerException és miért nehéz azonosítani. Aztán különböző módokat láttunk arra, hogy elkerüljük a kód redundanciáját az ellenőrzés körül nulla paraméterekkel, visszatérési típusokkal és egyéb változókkal.

Az összes példa elérhető a GitHub oldalon.