Spracovanie výnimiek v Jave

1. Prehľad

V tomto tutoriáli si prejdeme základy manipulácie s výnimkami v Jave, ako aj niektoré z jej funkcií.

2. Prvé princípy

2.1. Čo je to?

Aby sme lepšie pochopili výnimky a zaobchádzanie s nimi, urobme si porovnanie v reálnom živote.

Predstavte si, že si objednáme produkt online, ale na ceste bude zlyhanie dodávky. Dobrá spoločnosť dokáže tento problém vyriešiť a ladne presmerovať náš balík tak, aby stále prichádzal včas.

Rovnako tak v prostredí Java môže pri vykonávaní našich pokynov dôjsť k chybám. Dobre spracovanie výnimiek dokáže spracovať chyby a ladne presmerovať program, aby mal používateľ stále pozitívny zážitok.

2.2. Prečo to používať?

Kód obvykle píšeme v idealizovanom prostredí: súborový systém vždy obsahuje naše súbory, sieť je v poriadku a JVM má vždy dostatok pamäte. Niekedy tomu hovoríme „šťastná cesta“.

Vo výrobe však môže dôjsť k poškodeniu súborových systémov, poruchám sietí a nedostatku pamäte JVM. Blahobyt nášho kódu závisí od toho, ako sa správa k „nešťastným cestám“.

Tieto podmienky musíme zvládnuť, pretože negatívne ovplyvňujú tok žiadosti a formujú sa výnimky:

public static List getPlayers () hodí IOException {Path path = Paths.get ("players.dat"); Zoznam hráčov = Files.readAllLines (cesta); vrátiť players.stream () .map (Player :: new) .collect (Collectors.toList ()); }

Tento kód sa rozhodol nespracovať Výnimka IO, namiesto toho ho odovzdá do zásobníka hovorov. V idealizovanom prostredí kód funguje dobre.

Čo by sa však mohlo stať vo výrobe, keby players.dat chýba?

Výnimka vo vlákne „hlavný“ java.nio.file.NoSuchFileException: súbor players.dat <- players.dat neexistuje na sun.nio.fs.WindowsException.translateToIOException (neznámy zdroj) na sun.nio.fs.WindowsException .rethrowAsIOException (neznámy zdroj) // ... viac trasovania zásobníka na java.nio.file.Files.readAllLines (neznámy zdroj) na java.nio.file.Files.readAllLines (neznámy zdroj) na adrese Exceptions.getPlayers (Exceptions.java) : 12) <- Výnimka vzniká v metóde getPlayers (), na riadku 12 na Exceptions.main (Exceptions.java:19) <- getPlayers () volá main (), na riadku 19

Bez zvládnutia tejto výnimky môže inak zdravý program úplne prestať bežať! Musíme sa uistiť, že náš kód má plán, keď sa situácia pokazí.

Tu si okrem výnimiek všimnite ešte jednu výhodu, a to je samotné sledovanie zásobníka. Kvôli tomuto sledovaniu zásobníka môžeme často presne určiť nevhodný kód bez potreby pripojiť debugger.

3. Hierarchia výnimiek

Nakoniec výnimky sú iba objekty Java a všetky siahajú od Hoditeľné:

 ---> Chyba vyhadzovateľnej výnimky | (začiarknuté) (nezačiarknuté) RuntimeException (nezačiarknuté)

Existujú tri hlavné kategórie výnimočných podmienok:

  • Skontrolované výnimky
  • Nezačiarknuté výnimky / výnimky za behu
  • Chyby

Runtime a nezaškrtnuté výnimky odkazujú na to isté. Často ich môžeme zameniť.

3.1. Začiarknuté výnimky

Zaškrtnuté výnimky sú výnimky, s ktorými nás kompilátor Java vyžaduje. Výnimku musíme buď deklaratívne vyhodiť do zásobníka hovorov, alebo si ju musíme vyriešiť sami. O oboch viac za chvíľu.

Dokumentácia spoločnosti Oracle nám hovorí, aby sme použili zaškrtnuté výnimky, keď môžeme rozumne očakávať, že sa volajúci našej metódy bude môcť zotaviť.

Niekoľko príkladov kontrolovaných výnimiek je Výnimka IO a ServletException.

3.2. Nezačiarknuté výnimky

Nekontrolované výnimky sú výnimky, ktoré robí kompilátor Java nie vyžadujú, aby sme to zvládli.

Jednoducho povedané, ak vytvoríme výnimku, ktorá sa rozširuje RuntimeException, nebude začiarknuté; inak to bude skontrolované.

Aj keď to znie pohodlne, dokumentácia spoločnosti Oracle nám hovorí, že pre oba koncepty existujú dobré dôvody, napríklad rozlišovanie medzi situačnou chybou (začiarknuté) a chybou použitia (nezačiarknuté).

Niektoré príklady nekontrolovaných výnimiek sú NullPointerException, IllegalArgumentException, a Výnimka zabezpečenia.

3.3. Chyby

Chyby predstavujú vážne a zvyčajne nedobytné podmienky, ako napríklad nekompatibilita knižnice, nekonečná rekurzia alebo úniky pamäte.

A to aj napriek tomu, že sa nepredlžujú RuntimeException, tiež nie sú začiarknuté.

Vo väčšine prípadov by bolo pre nás čudné manipulovať s nimi, vytvárať ich inštancie alebo ich rozširovať Chyby. Zvyčajne chceme, aby sa tieto množili úplne hore.

Niekoľko príkladov chýb je a StackOverflowError a OutOfMemoryError.

4. Spracovanie výnimiek

V rozhraní Java API je veľa miest, kde sa môžu vyskytnúť chyby. Niektoré z týchto miest sú označené výnimkami, a to buď v podpise, alebo v Javadoc:

/ ** * @exception FileNotFoundException ... * / public Scanner (String fileName) throws FileNotFoundException {// ...}

Ako už bolo uvedené o niečo skôr, keď hovoríme týmto „rizikovým“ metódam, sme musieť vybavíme zaškrtnuté výnimky a my smieť vybaviť neoznačené. Java nám poskytuje niekoľko spôsobov, ako to dosiahnuť:

4.1. hodí

Najjednoduchší spôsob, ako „zvládnuť“ výnimku, je zmeniť ju:

public int getPlayerScore (String playerFile) hodí FileNotFoundException {obsah skenera = nový skener (nový súbor (playerFile)); return Integer.parseInt (contents.nextLine ()); }

Pretože FileNotFoundException je zaškrtnutá výnimka, toto je najjednoduchší spôsob, ako uspokojiť kompilátor, ale Znamená to, že každý, kto volá našu metódu, to teraz musí zvládnuť tiež!

parseInt môže hodiť a NumberFormatException, ale pretože nie je začiarknuté, nie je potrebné, aby sme s tým manipulovali.

4.2. Skús chytiť

Ak sa chceme pokúsiť zvládnuť výnimku sami, môžeme použiť a Skús chytiť blokovať. Zvládneme to prerobením našej výnimky:

public int getPlayerScore (String playerFile) {try {obsah skenera = nový skener (nový súbor (playerFile)); return Integer.parseInt (contents.nextLine ()); } catch (FileNotFoundException noFile) {hodiť novú IllegalArgumentException ("Súbor sa nenašiel"); }}

Alebo vykonaním krokov na obnovenie:

public int getPlayerScore (String playerFile) {try {obsah skenera = nový skener (nový súbor (playerFile)); return Integer.parseInt (contents.nextLine ()); } catch (FileNotFoundException noFile) {logger.warn ("Súbor sa nenašiel, obnovuje sa skóre."); návrat 0; }}

4.3. konečne

Teraz existujú chvíle, keď máme kód, ktorý je potrebné vykonať bez ohľadu na to, či dôjde k výnimke, a práve tu konečne prichádza kľúčové slovo.

V našich doterajších príkladoch sa v tieni skrýva nepríjemná chyba, ktorá spočíva v tom, že Java predvolene nevracia operačné systémy súborov do operačného systému.

Bez ohľadu na to, či si dokážeme súbor prečítať alebo nie, sa chceme ubezpečiť, že urobíme príslušné vyčistenie!

Skúsme to najskôr „lenivo“:

public int getPlayerScore (String playerFile) hodí FileNotFoundException {obsah skenera = null; skus {obsah = novy skener (novy subor (playerFile)); return Integer.parseInt (contents.nextLine ()); } nakoniec {if (contents! = null) {contents.close (); }}} 

Tu je konečne blok označuje, aký kód chceme, aby Java bežala bez ohľadu na to, čo sa stane s pokusom o prečítanie súboru.

Aj keď a FileNotFoundException je zvrhnutý zásobník hovorov, Java zavolá obsah konečne predtým, ako to urobíte.

Výnimku môžeme zvládnuť aj obaja a uistite sa, že sú naše zdroje zatvorené:

public int getPlayerScore (String playerFile) {obsah skenera; skus {obsah = novy skener (novy subor (playerFile)); return Integer.parseInt (contents.nextLine ()); } catch (FileNotFoundException noFile) {logger.warn ("Súbor sa nenašiel, obnovuje sa skóre."); návrat 0; } konečne {vyskúšať {if (obsah! = null) {obsah.zavrieť (); }} catch (IOException io) {logger.error ("Čítačku sa nepodarilo zavrieť!", io); }}}

Pretože Zavrieť je tiež „riskantná“ metóda, musíme tiež chytiť jej výnimku!

Môže to vyzerať dosť komplikovane, ale potrebujeme, aby každý kúsok zvládol každý potenciálny problém, ktorý môže vzniknúť správne.

4.4. skús- so zdrojmi

Našťastie od verzie Java 7 môžeme vyššie uvedenú syntax zjednodušiť pri práci s vecami, ktoré sa rozširujú Automatické uzatváranie:

public int getPlayerScore (String playerFile) {try (Scanner contents = new Scanner (new File (playerFile)))) {return Integer.parseInt (contents.nextLine ()); } catch (FileNotFoundException e) {logger.warn ("Súbor sa nenašiel, obnovuje sa skóre."); návrat 0; }}

Keď umiestnime referencie, ktoré sú Automatické uzatváranie v skús deklaráciu, potom zdroj nemusíme sami uzatvárať.

Stále môžeme použiť a konečne blokujte, aby ste vykonali akýkoľvek iný druh vyčistenia, aký chceme.

Prečítajte si náš článok venovaný skús-s-zdrojmi, aby ste sa dozvedeli viac.

4.5. Viacnásobné chytiť Bloky

Niekedy môže kód vyvolať viac ako jednu výnimku a my môžeme mať viac ako jednu chytiť blok zvládnuť každý jednotlivo:

public int getPlayerScore (String playerFile) {try (Scanner contents = new Scanner (new File (playerFile)))) {return Integer.parseInt (contents.nextLine ()); } catch (IOException e) {logger.warn ("Súbor hráča by sa nenačítal!", e); návrat 0; } catch (NumberFormatException e) {logger.warn ("Súbor hráča bol poškodený!", e); návrat 0; }}

Viaceré úlovky nám dávajú možnosť v prípade potreby zvládnuť každú výnimku inak.

Tu si všimnite aj to, že sme nechytili FileNotFoundException, a je to preto rozširuje IOException. Pretože chytáme Výnimka IO, Java bude považovať každú zo svojich podtried aj za spracovanú.

Povedzme si však, že musíme liečiť FileNotFoundException odlišne od všeobecnejších Výnimka IO:

public int getPlayerScore (String playerFile) {try (Scanner contents = new Scanner (new File (playerFile)))) {return Integer.parseInt (contents.nextLine ()); } catch (FileNotFoundException e) {logger.warn ("Súbor prehrávača sa nenašiel!", e); návrat 0; } catch (IOException e) {logger.warn ("Súbor hráča by sa nenačítal!", e); návrat 0; } catch (NumberFormatException e) {logger.warn ("Súbor hráča bol poškodený!", e); návrat 0; }}

Java nám umožňuje spracovávať výnimky podtried zvlášť, nezabudnite ich umiestniť vyššie v zozname úlovkov.

4.6. Únie chytiť Bloky

Keď vieme, že spôsob, akým narábame s chybami, bude rovnaký, program Java 7 predstavil možnosť zachytiť viac výnimiek v rovnakom bloku:

public int getPlayerScore (String playerFile) {try (Scanner contents = new Scanner (new File (playerFile)))) {return Integer.parseInt (contents.nextLine ()); } catch (IOException | NumberFormatException e) {logger.warn ("Nepodarilo sa načítať skóre!", e); návrat 0; }}

5. Hádzacie výnimky

Ak nechceme výnimku zvládnuť sami alebo ak chceme generovať naše výnimky pre ostatných, musíme sa s nimi oboznámiť hodiť kľúčové slovo.

Povedzme, že máme nasledujúcu kontrolovanú výnimku, ktorú sme si sami vytvorili:

public class TimeoutException extends Exception {public TimeoutException (String message) {super (message); }}

a máme metódu, ktorej dokončenie môže potenciálne trvať dlho:

verejný zoznam loadAllPlayers (String playersFile) {// ... potenciálne dlhá prevádzka}

5.1. Vyhodenie zaškrtnutej výnimky

Rovnako ako návrat z metódy, môžeme hodiť kedykoľvek.

Mali by sme samozrejme hodiť, keď sa pokúšame naznačiť, že sa niečo pokazilo:

public List loadAllPlayers (String playersFile) hodí TimeoutException {while (! tooLong) {// ... potenciálne dlhá operácia} hodiť novú TimeoutException ("Táto operácia trvala príliš dlho"); }

Pretože Výnimka časového limitu je začiarknuté, musíme tiež použiť hodí kľúčové slovo v podpise, aby volajúci našej metódy vedeli, že s nimi bude pracovať.

5.2. Hoďnekontrolovaná výnimka

Ak chceme urobiť niečo ako, napríklad, overiť vstup, môžeme namiesto toho použiť neoznačenú výnimku:

public List loadAllPlayers (String playersFile) throws TimeoutException {if (! isFilenameValid (playersFile)) {throw new IllegalArgumentException ("Filename is valid!"); } // ...} 

Pretože IllegalArgumentException nie je začiarknuté, metódu nemusíme označovať, aj keď sme vítaní.

Niektorí metódu aj tak označujú ako formu dokumentácie.

5.3. Zbalenie a prehodenie

Môžeme sa tiež rozhodnúť znova vytvoriť výnimku, ktorú sme chytili:

public List loadAllPlayers (String playersFile) hodí IOException {try {// ...} catch (IOException io) {throw io; }}

Alebo urobte zalomenie a znova pretvorte:

public List loadAllPlayers (String playersFile) hodí PlayerLoadException {try {// ...} catch (IOException io) {throw new PlayerLoadException (io); }}

To môže byť pekné pri konsolidácii mnohých rôznych výnimiek do jednej.

5.4. Prehodnocovanie Hoditeľné alebo Výnimka

Teraz pre špeciálny prípad.

Ak sú jediné možné výnimky, ktoré by daný blok kódu mohol vyvolať, sú nezačiarknuté až na výnimky, potom môžeme chytiť a znova prerobiť Hoditeľné alebo Výnimka bez ich pridania k nášmu podpisu metódy:

public List loadAllPlayers (String playersFile) {try {throw new NullPointerException (); } chytiť (hoditeľné t) {hodiť t; }}

Aj keď je to jednoduché, vyššie uvedený kód nemôže hádzať kontrolovanú výnimku a z toho dôvodu, aj keď znova otvárame kontrolovanú výnimku, nemusíme podpis označovať znakom hodí doložka.

To je užitočné pri triedach a metódach proxy serverov. Viac o tomto nájdete tu.

5.5. Dedenie

Keď označíme metódy a hodí kľúčové slovo, má to vplyv na to, ako môžu podtriedy prepísať našu metódu.

Za okolností, keď naša metóda vyvolá kontrolovanú výnimku:

verejné triedy Výnimky {public List loadAllPlayers (String playersFile) hodí TimeoutException {// ...}}

Podtrieda môže mať „menej riskantný“ podpis:

public class FewerExceptions extends Exceptions {@Override public List loadAllPlayers (String playersFile) {// overridden}}

Ale nie „viac rizikovejší “podpis:

public class MoreExceptions extends Exceptions {@Override public List loadAllPlayers (String playersFile) throws MyCheckedException {// overridden}}

Je to tak preto, lebo zmluvy sú v čase kompilácie určené typom referencie. Ak vytvorím inštanciu Viac výnimiek a uložiť do Výnimky:

Výnimky výnimky = nové MoreExceptions (); exceptions.loadAllPlayers ("súbor");

Potom mi JVM iba povie chytiť the Výnimka časového limitu, čo je nesprávne, pretože som to povedal MoreExceptions # loadAllPlayers hodí inú výnimku.

Jednoducho povedané, podtriedy môžu hádzať menej kontrolované výnimky ako ich nadtrieda, ale nie viac.

6. Anti-vzory

6.1. Výnimky z prehĺtania

Teraz existuje ešte jeden spôsob, ako by sme mohli kompilátora uspokojiť:

public int getPlayerScore (String playerFile) {try {// ...} catch (Exception e) {} // <== chytiť a prehltnúť návrat 0; }

Vyššie uvedené sa nazývaprehltnutie výnimky. Väčšinou by to pre nás bolo trochu zlé, pretože by sa tým problém neriešil a okrem toho bráni ďalšiemu kódu v riešení problému.

Sme si istí, že niekedy dôjde k zaškrtnutej výnimke, že sa tak nikdy nestane. V týchto prípadoch by sme mali ešte aspoň pridať komentár o tom, že sme zámerne zjedli výnimku:

public int getPlayerScore (String playerFile) {try {// ...} catch (IOException e) {// toto sa nikdy nestane}}

Ďalším spôsobom, ako môžeme „prehltnúť“ výnimku, je vytlačiť si výnimku z chybového toku jednoducho:

public int getPlayerScore (String playerFile) {try {// ...} catch (Výnimka e) {e.printStackTrace (); } návrat 0; }

Trochu sme zlepšili našu situáciu tým, že sme chybu aspoň napísali niekde pre neskoršiu diagnostiku.

Bolo by však lepšie, keby sme používali záznamník:

public int getPlayerScore (String playerFile) {try {// ...} catch (IOException e) {logger.error ("Nepodarilo sa načítať skóre", e); návrat 0; }}

Aj keď je pre nás veľmi výhodné vybavovať výnimky týmto spôsobom, musíme sa uistiť, že neprehĺtame dôležité informácie, ktoré by volajúci nášho kódu mohli použiť na odstránenie problému.

Nakoniec môžeme neúmyselne prehltnúť výnimku tak, že ju nezahrnieme ako príčinu, keď vrháme novú výnimku:

public int getPlayerScore (String playerFile) {try {// ...} catch (IOException e) {throw new PlayerScoreException (); }} 

Tu sa potľapkáme po chrbte za upozornenie volajúceho na chybu, ale nepodarilo sa nám zahrnúť Výnimka IO ako príčina. Z tohto dôvodu sme stratili dôležité informácie, ktoré by mohli volajúci alebo operátori použiť na diagnostiku problému.

Bolo by lepšie, keby sme robili:

public int getPlayerScore (String playerFile) {try {// ...} catch (IOException e) {throw new PlayerScoreException (e); }}

Všimnite si jemný rozdiel vrátane Výnimka IO ako príčina z PlayerScoreException.

6.2. Použitím návrat v konečne Blokovať

Ďalším spôsobom, ako prehltnúť výnimky, je návrat z konečne blokovať. To je zlé, pretože pri náhlom návrate JVM zruší výnimku, aj keď bola vyhodená z nášho kódu:

public int getPlayerScore (String playerFile) {int skóre = 0; skus {hodit novu IOException (); } konečne {návrat skóre; // <== IOException je zrušená}}

Podľa špecifikácie jazyka Java:

Ak sa vykonanie bloku pokusu náhle dokončí z iného dôvodu R, potom sa vykoná blok konečne a potom je na výber.

Ak konečne blok sa dokončí normálne, potom sa príkaz try skompletizuje z dôvodu R.

Ak konečne blok sa náhle dokončí z dôvodu S, potom sa príkaz try dokončí náhle z dôvodu S (a dôvod R sa zahodí).

6.3. Použitím hodiť v konečne Blokovať

Podobné ako pri použití návrat v konečne blok, výnimka hodená v a konečne blok bude mať prednosť pred výnimkou, ktorá vznikne v bloku catch.

Toto „vymaže“ pôvodnú výnimku z skús blok a stratíme všetky tieto cenné informácie:

public int getPlayerScore (String playerFile) {try {// ...} catch (IOException io) {throw new IllegalStateException (io); // <== zjedený konečne} konečne {hodiť nový OtherException (); }}

6.4. Použitím hodiť ako ísť do

Niektorí ľudia tiež podľahli pokušeniu používať hodiť ako ísť do vyhlásenie:

public void doSomething () {try {// veľa kódu hodiť nový MyException (); // druhá skupina kódu} úlovok (MyException e) {// tretia skupina kódu}}

Je to zvláštne, pretože sa kód pokúša používať výnimky na riadenie toku na rozdiel od spracovania chýb.

7. Bežné výnimky a chyby

Tu uvádzame niektoré bežné výnimky a chyby, s ktorými sa občas všetci stretneme:

7.1. Začiarknuté výnimky

  • Výnimka IO - Táto výnimka je zvyčajne spôsob, ako povedať, že niečo v sieti, súborovom systéme alebo databáze zlyhalo.

7.2. Výnimky za behu

  • ArrayIndexOutOfBoundsException - táto výnimka znamená, že sme sa pokúsili získať prístup k neexistujúcemu indexu poľa, napríklad pri pokuse o získanie indexu 5 z poľa dĺžky 3.
  • ClassCastException - táto výnimka znamená, že sme sa pokúsili vykonať nelegálne obsadenie, napríklad pokus o konverziu a String do a Zoznam. Zvyčajne sa mu môžeme vyhnúť prevedením obrany inštancia kontroly pred nahodením.
  • IllegalArgumentException - táto výnimka je všeobecný spôsob, ako môžeme povedať, že jeden z poskytnutých parametrov metódy alebo konštruktora je neplatný.
  • IllegalStateException - Táto výnimka predstavuje všeobecný spôsob, ako môžeme povedať, že náš vnútorný stav je rovnako ako stav nášho objektu neplatný.
  • NullPointerException - Táto výnimka znamená, že sme sa pokúsili odkázať na a nulový objekt. Zvyčajne sa mu môžeme vyhnúť tým, že vykonáme defenzívu nulový kontroly alebo pomocou Voliteľné.
  • NumberFormatException - Táto výnimka znamená, že sme sa pokúsili previesť a String na číslo, ale reťazec obsahoval nelegálne znaky, ako napríklad pokus o prevod „5f3“ na číslo.

7.3. Chyby

  • StackOverflowError - táto výnimka znamená, že trasovanie zásobníka je príliš veľké. To sa niekedy môže stať v masívnych aplikáciách; zvyčajne to však znamená, že sa v našom kóde deje nejaká nekonečná rekurzia.
  • NoClassDefFoundError - táto výnimka znamená, že sa triede nepodarilo načítať buď z dôvodu, že nie je na ceste k triede, alebo z dôvodu zlyhania statickej inicializácie.
  • OutOfMemoryError - táto výnimka znamená, že JVM nemá k dispozícii viac pamäte na alokáciu pre viac objektov. Niekedy je to spôsobené únikom pamäte.

8. Záver

V tomto článku sme si prešli základmi spracovania výnimiek a niekoľkými príkladmi dobrej a nesprávnej praxe.

Ako vždy, všetok kód nájdený v tomto článku nájdete na GitHub!


$config[zx-auto] not found$config[zx-overlay] not found