Kivételkezelés Java-ban

1. Áttekintés

Ebben az oktatóanyagban áttekintjük a Java-ban végzett kivételkezelés alapjait, valamint annak néhány részletét.

2. Első elvek

2.1. Mi az?

A kivételek és a kivételkezelés jobb megértése érdekében tegyünk egy valós összehasonlítást.

Képzelje el, hogy online megrendelünk egy terméket, de útközben meghiúsul a szállítás. Egy jó társaság képes kezelni ezt a problémát, és kecsesen átirányítani a csomagunkat úgy, hogy az még mindig időben megérkezzen.

Hasonlóképpen, a Java-ban a kód hibákat tapasztalhat utasításainak végrehajtása közben. Jó kivételkezelés képes kezelni a hibákat és kecsesen átirányítani a programot, hogy a felhasználó továbbra is pozitív élményt nyújtson.

2.2. Miért használja?

A kódot általában idealizált környezetben írjuk: a fájlrendszer mindig tartalmazza a fájljainkat, a hálózat egészséges, és a JVM mindig rendelkezik elegendő memóriával. Néha ezt hívjuk „boldog útnak”.

A termelésben azonban a fájlrendszerek megrongálódhatnak, a hálózatok meghibásodhatnak, és a JVM-ekből kifogy a memória. Kódexünk jóléte attól függ, hogy miként kezeli a „boldogtalan utakat”.

Azért kell kezelnünk ezeket a feltételeket, mert negatívan és formában befolyásolják az alkalmazás folyamatát kivételek:

public static List getPlayers () dobja az IOException-t {Path path = Paths.get ("players.dat"); Játékosok listája = Files.readAllLines (elérési út); visszatérő játékosok.stream () .map (Player :: new) .collect (Collectors.toList ()); }

Ez a kód úgy dönt, hogy nem kezeli a IOException, helyette továbbítja a hívásveremben. Idealizált környezetben a kód jól működik.

De mi történhet a gyártásban, ha játékosok.dat hiányzik?

Kivétel a "main" szálban java.nio.file.NoSuchFileException: players.dat <- players.dat fájl nem létezik a sun.nio.fs.WindowsException.translateToIOException (Ismeretlen forrás) a sun.nio.fs.WindowsException címen .rethrowAsIOException (Ismeretlen forrás) // ... további veremkövetés a java.nio.file.Files.readAllLines (Ismeretlen forrás) címen java.nio.file.Files.readAllLines (Ismeretlen forrás) az Exceptions.getPlayers (Exceptions.java) oldalon : 12) <- Kivétel adódik a getPlayers () metódusban, a 12. sorban az Exceptions.main oldalon (Exceptions.java:19) <- a getPlayers () -t a main () hívja meg a 19. sorban

E kivétel kezelése nélkül az egyébként egészséges program teljesen leállhat! Győződnünk kell arról, hogy a kódunk rendelkezik-e tervvel arra nézve, amikor a dolgok rosszra fordulnak.

Itt is említsen még egy előnyt a kivételeknek, és ez maga a verem nyomkövetés. Ezen veremkövetés miatt gyakran pontosan meghatározhatjuk a megsértő kódot anélkül, hogy hibakeresőt kellene csatolnunk.

3. Kivételi hierarchia

Végül, kivételek csak Java objektumok, amelyek mindegyike kiterjed Dobható:

 ---> Dobható kivétel hiba | (bejelölt) (nem ellenőrzött) RuntimeException (nincs bejelölve)

A kivételes körülményeknek három fő kategóriája van:

  • Ellenőrzött kivételek
  • Ellenőrizetlen kivételek / Futásidejű kivételek
  • Hibák

A futásidő és az ellenőrizetlen kivételek ugyanarra utalnak. Gyakran használhatjuk felcserélhető módon.

3.1. Ellenőrzött kivételek

Az ellenőrzött kivételek azok a kivételek, amelyek kezelését a Java fordító megköveteli tőlünk. Vagy kifejezetten fel kell dobnunk a kivételt a hívásverembe, vagy magunknak kell kezelnünk. Egy pillanat alatt többet mindkettőről.

Az Oracle dokumentációja azt mondja, hogy használjunk ellenőrzött kivételeket, amikor ésszerűen elvárhatjuk, hogy módszerünk hívója helyreálljon.

Néhány példa az ellenőrzött kivételekre IOException és ServletException.

3.2. Ellenőrizetlen kivételek

Az ellenőrizetlen kivételek azok a kivételek, amelyeket a Java fordító végez nem megkövetelik tőlünk, hogy kezeljük.

Egyszerűen fogalmazva, ha létrehozunk egy kiterjedő kivételt RuntimeException, ez nem lesz ellenőrizve; különben ellenőrizni fogják.

És bár ez jól hangzik, az Oracle dokumentációja elmondja, hogy mindkét koncepciónak jó okai vannak, például megkülönböztetni egy szituációs hibát (ellenőrzött) és egy használati hibát (nem ellenőrzött).

Néhány példa az ellenőrizetlen kivételekre NullPointerException, IllegalArgumentException, és SecurityException.

3.3. Hibák

A hibák olyan súlyos és általában helyrehozhatatlan körülményeket jelentenek, mint például a könyvtár inkompatibilitása, végtelen rekurzió vagy memóriaszivárgás.

És bár nem nyúlnak ki RuntimeException, ők is ellenőrizetlenek.

A legtöbb esetben furcsa lenne számunkra kezelni, példázni vagy kiterjeszteni Hibák. Általában azt akarjuk, hogy ezek végig terjedjenek.

Néhány példa a hibákra a StackOverflowError és OutOfMemoryError.

4. Kivételek kezelése

A Java API-ban rengeteg olyan hely van, ahol a dolgok elromolhatnak, és ezek közül néhányat kivételekkel jelölnek meg, akár az aláírás, akár a Javadoc:

/ ** * @exception FileNotFoundException ... * / public scanner (String fileName) dobja a FileNotFoundException {// ...}

Ahogy egy kicsit korábban megállapítottuk, amikor ezeket „kockázatos” módszereknek nevezzük, mi kell kezeljük az ellenőrzött kivételeket, és mi lehet kezelje az ellenőrizetleneket. A Java ennek számos módját kínálja:

4.1. dob

A kivétel „kezelésének” legegyszerűbb módja annak újravetése:

public int getPlayerScore (String playerFile) dobja a FileNotFoundException {Scanner tartalma = new Scanner (új File (playerFile)); return Integer.parseInt (content.nextLine ()); }

Mivel FileNotFoundException ellenőrzött kivétel, ez a legegyszerűbb módszer a fordító kielégítésére, de ez azt jelenti, hogy bárkinek, aki hívja a módszerünket, most azt is kezelnie kell!

parseInt dobhat a NumberFormatException, de mivel nincs ellenőrizve, nem kell kezelnünk.

4.2. próbáld elkapni

Ha magunk akarjuk kipróbálni a kivételt, használhatjuk a próbáld elkapni Blokk. Kezelhetjük úgy, hogy átírjuk a kivételünket:

public int getPlayerScore (String playerFile) {try {Szkenner tartalma = új Szkenner (új Fájl (playerFile)); return Integer.parseInt (content.nextLine ()); } catch (FileNotFoundException noFile) {dobjon új IllegalArgumentException-t ("A fájl nem található"); }}

Vagy helyreállítási lépések végrehajtásával:

public int getPlayerScore (String playerFile) {try {Szkenner tartalma = új Szkenner (új Fájl (playerFile)); return Integer.parseInt (content.nextLine ()); } catch (FileNotFoundException noFile) {logger.warn ("A fájl nem található, a pontszám visszaállítása."); visszatér 0; }}

4.3. végül

Vannak olyan esetek, amikor van olyan kódunk, amelyet végre kell hajtani, függetlenül attól, hogy történik-e kivétel, és itt a végül kulcsszó bejön.

Eddigi példáinkban csúnya hiba lapult az árnyékban, vagyis a Java alapértelmezés szerint nem adja vissza a fájlkezelőket az operációs rendszerbe.

Természetesen, függetlenül attól, hogy elolvassuk-e a fájlt, vagy sem, meg akarunk győződni arról, hogy elvégezzük a megfelelő tisztítást!

Próbáljuk ki először ezt a „lusta” módszert:

public int getPlayerScore (String playerFile) dobja a FileNotFoundException {Szkenner tartalma = null; próbáld ki a {tartalom = új Szkenner (új Fájl (playerFile)); return Integer.parseInt (content.nextLine ()); } végül {if (tartalom! = null) {content.close (); }}} 

Itt a végül blokk jelzi, hogy milyen kódot akarunk futtatni a Java-tól, függetlenül attól, hogy mi történik a fájl elolvasásával.

Még akkor is, ha a FileNotFoundException feldobja a hívásköteget, a Java meghívja a végül mielőtt ezt megtenné.

Mindketten kezelhetjük a kivételt is és győződjön meg arról, hogy erőforrásaink bezáródnak:

public int getPlayerScore (String playerFile) {Szkenner tartalma; próbáld ki a {tartalom = új Szkenner (új Fájl (playerFile)); return Integer.parseInt (content.nextLine ()); } catch (FileNotFoundException noFile) {logger.warn ("A fájl nem található, a pontszám visszaállítása."); visszatér 0; } végül {próbáld ki {if (tartalom! = null) {content.close (); }} catch (IOException io) {logger.error ("Nem sikerült bezárni az olvasót!", io); }}}

Mivel Bezárás szintén „kockázatos” módszer, meg kell fognunk a kivételét is!

Ez meglehetősen bonyolultnak tűnhet, de minden darabra szükségünk van minden helyesen felmerülő lehetséges problémához.

4.4. próbáld ki-forrásokkal

Szerencsére a Java 7-től kezdve egyszerűsíthetjük a fenti szintaxist, amikor kiterjedt dolgokkal dolgozunk Automatikusan zárható:

public int getPlayerScore (String playerFile) {try (Szkenner tartalma = új Szkenner (új Fájl (playerFile))) {return Integer.parseInt (content.nextLine ()); } catch (FileNotFoundException e) {logger.warn ("A fájl nem található, a pontszám visszaállítása."); visszatér 0; }}

Amikor olyan referenciákat helyezünk el, amelyek vannak Automatikusan zárható ban,-ben próbáld ki nyilatkozatot, akkor nem kell magunknak bezárnunk az erőforrást.

Még mindig használhatjuk az a végül blokkolja, bármi más jellegű takarítást végezhetünk.

Nézze meg a következő cikkünket próbáld ki-forrásokkal többet megtudni.

4.5. Többszörös fogás Blokkok

Néha a kód több kivételt is felvethet, és több is lehet nálunk fogás blokkfogantyú külön-külön:

public int getPlayerScore (String playerFile) {try (Szkenner tartalma = új Szkenner (új Fájl (playerFile))) {return Integer.parseInt (content.nextLine ()); } catch (IOException e) {logger.warn ("A lejátszó fájl nem töltődne be!", e); visszatér 0; } catch (NumberFormatException e) {logger.warn ("A lejátszó fájl sérült!", e); visszatér 0; }}

A többszörös elkapás lehetőséget ad arra, hogy az egyes kivételeket másként kezeljük, ha erre szükség van.

Itt jegyezzük meg azt is, hogy nem fogtuk el FileNotFoundException, és ez azért van kiterjeszti az IOException-t. Mert elkapjuk IOException, A Java bármelyik alosztályát szintén kezelni fogja.

Mondjuk mégis, hogy kezelnünk kell FileNotFoundException eltérően az általánosabbaktól IOException:

public int getPlayerScore (String playerFile) {try (Szkenner tartalma = új Szkenner (új Fájl (playerFile))) {return Integer.parseInt (content.nextLine ()); } catch (FileNotFoundException e) {logger.warn ("A lejátszó fájl nem található!", e); visszatér 0; } catch (IOException e) {logger.warn ("A lejátszó fájl nem töltődne be!", e); visszatér 0; } catch (NumberFormatException e) {logger.warn ("A lejátszó fájl sérült!", e); visszatér 0; }}

A Java segítségével külön kezelhetjük az alosztály kivételeket, ne felejtsd el őket feljebb helyezni a fogások listáján.

4.6. Unió fogás Blokkok

Amikor tudjuk, hogy a hibák kezelése ugyanaz lesz, a Java 7 bevezette azt a lehetőséget, hogy több kivételt elkapjon ugyanabban a blokkban:

public int getPlayerScore (String playerFile) {try (Szkenner tartalma = új Szkenner (új Fájl (playerFile))) {return Integer.parseInt (content.nextLine ()); } catch (IOException | NumberFormatException e) {logger.warn ("Nem sikerült betölteni a pontszámot!", e); visszatér 0; }}

5. Dobó kivételek

Ha nem akarjuk magunkat kezelni a kivételt, vagy másoknak akarjuk generálni a kivételeket, akkor meg kell ismerkednünk a dobás kulcsszó.

Tegyük fel, hogy a következő ellenőrzött kivétel van, amelyet mi magunk hoztunk létre:

public class A TimeoutException kiterjeszti a Kivételt {public TimeoutException (String üzenet) {super (üzenet); }}

és van egy módszerünk, amelynek megvalósítása potenciálisan hosszú időt vehet igénybe:

public List loadAllPlayers (String playersFile) {// ... potenciálisan hosszú működés}

5.1. Ellenőrzött kivétel dobása

Mint visszatérhetünk egy módszerről, mi is dobás bármikor.

Természetesen akkor kell dobnunk, amikor megpróbáljuk jelezni, hogy valami rosszul esett:

public List loadAllPlayers (String playersFile) dobja a TimeoutException {while (! tooLong) {// ... potenciálisan hosszú műveletet} dob új TimeoutException ("Ez a művelet túl sokáig tartott"); }

Mivel TimeoutException be van jelölve, akkor a dob kulcsszó az aláírásban, hogy a módszerünk hívói tudják kezelni.

5.2. Dobásellenőrzés nélküli kivétel

Ha valami olyasmit akarunk végrehajtani, mint például a bevitel validálása, használhatunk helyette egy nem ellenőrzött kivételt:

public List loadAllPlayers (String playersFile) dob TimeoutException {if (! isFilenameValid (playersFile)) {dob új IllegalArgumentException ("A fájlnév nem érvényes!"); } // ...} 

Mivel IllegalArgumentException nincs ellenőrizve, nem kell megjelölnünk a módszert, bár szívesen látjuk.

Néhányan a módszert egyébként dokumentációként jelölik meg.

5.3. Csomagolás és visszadobás

Választhatunk egy kivételt is, amelyet elkaptunk:

public List loadAllPlayers (String playersFile) dobja az IOException {try {// ...} catch (IOException io) {dob io; }}

Vagy végezzen burkolást és dobja újra:

public List loadAllPlayers (String playersFile) dobja a PlayerLoadException {try {// ...} catch (IOException io) {dob új PlayerLoadException (io); }}

Ez jó lehet, ha sokféle kivételt egyesítünk egybe.

5.4. Visszahajítás Dobható vagy Kivétel

Most egy különleges esetről.

Ha az egyetlen lehetséges kivétel, amelyet egy adott kódblokk felvethet, az ellenőrizetlen kivételeket, akkor megfoghatjuk és visszadobhatjuk Dobható vagy Kivétel anélkül, hogy hozzáadná őket a módszer aláírásához:

public List loadAllPlayers (String playersFile) {próbálkozzon {dobja az új NullPointerException () -t; } fogás (Dobható t) {dobás t; }}

Bár egyszerű, a fenti kód nem vethet be ellenőrzött kivételt, és emiatt, annak ellenére, hogy egy ellenőrzött kivételt újrarajzolunk, az aláírást nem kell dob kikötés.

Ez hasznos a proxy osztályokkal és módszerekkel. Erről többet itt talál.

5.5. Öröklés

Amikor a módszereket a-val jelöljük dob kulcsszó, hatással van arra, hogy az alosztályok felülírhatják a módszerünket.

Abban a esetben, amikor módszerünk bejelöli a kivételt:

public class Kivételek {public list loadAllPlayers (String playersFile) dob TimeoutException {// ...}}

Az alosztálynak „kevésbé kockázatos” aláírása lehet:

public class A FewerExceptions kiterjeszti a kivételeket {@Orride public list loadAllPlayers (String playersFile) {// felülbírálva}}

De nem egytöbb kockázatosabb ”aláírás:

public class A MoreExceptions kiterjeszti a kivételeket {@Orride public list loadAllPlayers (String playersFile) dobja a MyCheckedException {// felülbírálva}}

Ennek oka, hogy a szerződéseket fordításkor a referencia típusa határozza meg. Ha létrehozok egy példányát További kivételek és mentsd el Kivételek:

Kivételek kivételek = new MoreExceptions (); Kivételek.loadAllPlayers ("fájl");

Akkor a JVM csak azt mondja nekem fogás a TimeoutException, ami téves, mióta ezt mondtam MoreExceptions # loadAllPlayers más kivételt dob.

Egyszerűen fogalmazva, az alosztályok dobhatnak kevesebb ellenőrzött kivételeket, mint a szuperosztályukat, de nem több.

6. Antiminták

6.1. Kivételek lenyelése

Van egy másik módja annak, hogy megelégedhettünk volna a fordítóval:

public int getPlayerScore (String playerFile) {try {// ...} catch (e kivétel) {} // <== catch and swallow return 0; }

A fentieket hívjáklenyelve a kivételt. Legtöbbször kissé jelentőségteljes lenne ezt megtenni, mert nem foglalkozik ezzel a kérdéssel és megakadályozza, hogy más kód is kezelni tudja a problémát.

Vannak esetek, amikor be van jelölve egy kivétel, amely biztos abban, hogy soha nem fog megtörténni. Ezekben az esetekben még mindig hozzá kell tennünk egy megjegyzést, miszerint szándékosan ettük a kivételt:

public int getPlayerScore (String playerFile) {próbáld meg {// ...} catch (IOException e) {// erre soha nem kerül sor}}

Egy másik módja annak, hogy „lenyelhetünk” egy kivételt, az az, hogy a hibafolyam kivételét egyszerűen kinyomtatjuk:

public int getPlayerScore (String playerFile) {try {// ...} catch (e kivétel) {e.printStackTrace (); } return 0; }

Kicsit javítottunk a helyzetünkön, azzal, hogy a hibát legalább kiírtuk valahova a későbbi diagnózis érdekében.

Jobb lenne azonban, ha naplózót használnánk:

public int getPlayerScore (String playerFile) {try {// ...} catch (IOException e) {logger.error ("Nem sikerült betölteni a pontszámot", e); visszatér 0; }}

Bár számunkra nagyon kényelmes a kivételeket ilyen módon kezelni, meg kell győződnünk arról, hogy nem nyelünk le olyan fontos információkat, amelyeket a kódunk hívói felhasználhatnak a probléma orvoslására.

Végül akaratlanul is lenyelhetünk egy kivételt azzal, hogy nem vesszük fel okként, amikor új kivételt dobunk:

public int getPlayerScore (String playerFile) {próbáld {// ...} catch (IOException e) {dobj új PlayerScoreException (); }} 

Itt a hátunkon veregetjük magunkat, mert figyelmeztettük hívóinkat egy hibára, de nem tesszük bele a IOException mint okot. Emiatt elveszítettünk olyan fontos információkat, amelyeket a hívók vagy az üzemeltetők felhasználhattak a probléma diagnosztizálására.

Jobban járnánk:

public int getPlayerScore (String playerFile) {try {// ...} catch (IOException e) {dobj új PlayerScoreException-t (e); }}

Figyelje meg a különbség finom különbségét IOException mint a ok nak,-nek PlayerScoreException.

6.2. Használata Visszatérés a végül Blokk

A kivételek lenyelésének másik módja az Visszatérés tól végül Blokk. Ez azért rossz, mert a hirtelen visszatéréssel a JVM elveti a kivételt, még akkor is, ha a kódunk eldobta:

public int getPlayerScore (String playerFile) {int pontszám = 0; próbáld ki {dobj új IOException () -t; } végül {return score; // <== az IOException el van hagyva}}

A Java nyelvi specifikáció szerint:

Ha a try blokk végrehajtása bármilyen más okból hirtelen befejeződik R, akkor az utolsó blokk végrehajtásra kerül, és akkor van választási lehetőség.

Ha a végül A blokk normálisan befejeződik, majd a try utasítás R okból hirtelen befejeződik.

Ha a végül a blokk S okból hirtelen befejeződik, majd a try utasítás S ok miatt hirtelen befejeződik (és az R okot elvetik).

6.3. Használata dobás a végül Blokk

Hasonló a használatához Visszatérés a végül blokk, a kivétel a végül blokk elsőbbséget élvez a fogási blokkban felmerülő kivétellel szemben.

Ez „kitörli” az eredeti kivételt a próbáld ki blokkolja, és elveszítjük ezeket az értékes információkat:

public int getPlayerScore (String playerFile) {próbáld meg {// ...} catch (IOException io) {dobj új IllegalStateException (io); // <== megette a végül} végül {dobja az új OtherException () -t; }}

6.4. Használata dobás mint a menj

Néhány ember engedett a használat kísértésének is dobás mint a menj nyilatkozat:

public void doSomething () {try {// egy csomó kód dob új MyException (); // második csomó kód} fogás (MyException e) {// harmadik csomó kód}}

Ez furcsa, mert a kód a hibakezeléssel szemben próbál kivételeket használni az áramlásszabályozáshoz.

7. Gyakori kivételek és hibák

Íme néhány gyakori kivétel és hiba, amelyekbe időnként belefutunk:

7.1. Ellenőrzött kivételek

  • IOException - Ez a kivétel általában annak kijelentésére szolgál, hogy valami a hálózaton, a fájlrendszeren vagy az adatbázisban nem sikerült.

7.2. Futásidejű kivételek

  • ArrayIndexOutOfBoundsException - ez a kivétel azt jelenti, hogy megpróbáltunk hozzáférni egy nem létező tömbindexhez, például amikor az 5-ös indexet egy 3 hosszúságú tömbből próbáltuk megszerezni.
  • ClassCastException - ez a kivétel azt jelenti, hogy illegális szereposztást próbáltunk végrehajtani, például megpróbáltunk konvertálni egy Húr ba be Lista. Védekezéssel általában elkerülhetjük Például az öntés előtt ellenőrzi.
  • IllegalArgumentException - ez a kivétel általános módszer arra, hogy azt mondjuk, hogy a megadott módszer vagy konstruktor egyik paramétere érvénytelen.
  • IllegalStateException - Ez a kivétel általános módszer arra, hogy azt mondhassuk, hogy belső állapotunk, akárcsak tárgyunk állapota, érvénytelen.
  • NullPointerException - Ez a kivétel azt jelenti, hogy megpróbáltunk hivatkozni a nulla tárgy. Általában elkerülhetjük, ha védekezéssel teljesítünk nulla ellenőrzések vagy a Választható.
  • NumberFormatException - Ez a kivétel azt jelenti, hogy megpróbáltuk átalakítani a Húr számokká, de a karakterlánc illegális karaktereket tartalmazott, például megpróbálta számokká konvertálni az „5f3” -t.

7.3. Hibák

  • StackOverflowError - ez a kivétel azt jelenti, hogy a verem nyoma túl nagy. Ez néha tömeges alkalmazásokban fordulhat elő; ez azonban általában azt jelenti, hogy végtelen rekurzió történik a kódunkban.
  • NoClassDefFoundError - ez a kivétel azt jelenti, hogy egy osztály nem töltődött be, mert nem volt az osztályúton, vagy a statikus inicializálás sikertelensége miatt.
  • OutOfMemoryError - ez a kivétel azt jelenti, hogy a JVM-nek nincs több memóriája lefoglalható több objektum számára. Néha ennek oka a memória szivárgása.

8. Következtetés

Ebben a cikkben áttekintettük a kivételkezelés alapjait, valamint néhány jó és rossz gyakorlat példáját.

Mint mindig, a cikkben található összes kód megtalálható a GitHubon!