Výjimky v Javě
Z MiS
(Rozdíly mezi verzemi)
(Vytvořen obsah stránky.) |
(→Proč používat výjimky?: Přidáno throws do kódu, obarvení kódu, který řeší výjimku.) |
||
(Není zobrazeno 6 mezilehlých verzí od 1 uživatele.) | |||
Řádka 29: | Řádka 29: | ||
#* Pokud chyba nastane, zbytek bloku kódu se přeskočí a ihned se provede reakce na chybu (viz další krok). | #* Pokud chyba nastane, zbytek bloku kódu se přeskočí a ihned se provede reakce na chybu (viz další krok). | ||
# Poté popíšeme, jak zareagovat na jednotlivé typy chyb (jak zareagovat na výjimky):<br /> <code>catch (''TypVýjimky'' ''názevVýjimky'') { </code> ... jak zareagovat ... <code> } </code> | # Poté popíšeme, jak zareagovat na jednotlivé typy chyb (jak zareagovat na výjimky):<br /> <code>catch (''TypVýjimky'' ''názevVýjimky'') { </code> ... jak zareagovat ... <code> } </code> | ||
− | # A nakonec můžeme uvést kód, který se má provést | + | #* Počet typů chyb, na které reagujeme, není omezený. |
+ | # A nakonec můžeme (ale nemusíme) uvést kód, který se má provést bez ohledu na to, jestli nastala chyba:<br /> <code> finally { </code> ... Co se má stát vždy na konci bloku ... <code> } </code> | ||
− | + | ||
+ | |||
+ | == Šablona kódu ošetření výjimek == | ||
'''try''' { ''// Začni provádět kód, ve kterém může nastat chybový stav...'' | '''try''' { ''// Začni provádět kód, ve kterém může nastat chybový stav...'' | ||
Řádka 43: | Řádka 46: | ||
''// Co se má stát, pokud nastane chyba TypVýjimky2...'' | ''// Co se má stát, pokud nastane chyba TypVýjimky2...'' | ||
+ | |||
+ | ''// Takto můžeme popsat reakci na různé typy výjimek. | ||
} '''finally''' { | } '''finally''' { | ||
Řádka 48: | Řádka 53: | ||
''// Co se má stát každopádně po skončení bloku...'' | ''// Co se má stát každopádně po skončení bloku...'' | ||
''// Provede se i tehdy, kdy nastane neočekávaná chyba. | ''// Provede se i tehdy, kdy nastane neočekávaná chyba. | ||
+ | |||
+ | ''// Část finally není povinná, může se vynechat... | ||
} | } | ||
Řádka 54: | Řádka 61: | ||
== Příklad — práce se souborem == | == Příklad — práce se souborem == | ||
+ | * Předpokládejme, že máme soubor, kde jsou na každém řádku údaje o jednom pozemku. | ||
+ | * Na každém řádku vždy uchováváme: | ||
+ | ** šířku pozemku (celé číslo) | ||
+ | ** délku pozemku (celé číslo) | ||
+ | ** popis pozemku (text) | ||
+ | * Položky jsou na řádku odděleny středníkem. | ||
+ | * Budeme postupně načítat jednotlivé řádky souboru a vypisovat je na obrazovku. | ||
+ | * Pokud nastane chyba, načítání se přeruší a aplikace vypíše vhodné chybové hlášení. | ||
+ | * Nakonec se vždy pokusíme soubor uzavřít. I v průběhu uzavírání souboru ale může dojít k chybě. | ||
+ | import java.util.Scanner; | ||
+ | import java.io.*; | ||
+ | import java.util.InputMismatchException; | ||
+ | ... | ||
+ | |||
+ | String nazev = "vstup.txt"; | ||
+ | int cislo = 0; | ||
+ | Scanner sc = null; | ||
+ | '''try''' { | ||
+ | sc = new Scanner(new File(nazev), "windows-1250"); | ||
+ | sc.useDelimiter("\\s*,\\s*|\\s*\r\n"); ''// Nastaví jako oddělovač položek čárku či konec řádku, mezery a tabulátory kolem čárky ignoruje.'' | ||
+ | while (sc.hasNext()) { | ||
+ | cislo = sc.nextInt(); | ||
+ | int sirka = cislo; | ||
+ | cislo = sc.nextInt(); | ||
+ | int delka = cislo; | ||
+ | String popis = sc.next(); | ||
+ | System.out.println("Pozemek: "+popis+" má šířku: "+sirka+" m a délku: "+delka+" m."); | ||
+ | System.out.println("Plocha pozemku je: "+(sirka*delka)+" m^2."); | ||
+ | System.out.println("---------------"); | ||
+ | } | ||
+ | } '''catch''' (FileNotFoundException ex) { System.err.println("Nenalezen soubor: "+nazev+"!"); | ||
+ | } '''catch''' (IOException ex) { System.err.println("Chyba čtení ze souboru "+nazev+": "+ex.getLocalizedMessage()); | ||
+ | } '''catch''' (InputMismatchException ex) { System.err.println("Nesprávný formát čísla v souboru "+nazev+"! Poslední správně načtené číslo je: "+cislo+"!"); | ||
+ | } '''finally''' { | ||
+ | sc.close(); | ||
+ | } | ||
+ | |||
+ | ... | ||
+ | |||
+ | <div class="Poznamka"> | ||
+ | Čtení textových souborů pomocí třídy <code>Scanner</code> je vhodný pouze pro velmi jednoduché textové soubory! | ||
+ | |||
+ | Pokud chcete vážně pracovat s CSV soubory, použijte některou z dostupných knihoven! Například: Univocity CSV parser! | ||
+ | </div> | ||
+ | |||
+ | |||
+ | |||
+ | == Delegování chyb <code>throws</code> == | ||
+ | * Může se také stát, že máme metodu, ve které nastává chyba, ale my nevíme, jak ji ošetřit (jak na ni správně zareagovat). | ||
+ | * V takovém případě můžeme použít klíčové slovo <code>throws</code>, kterým zařídíme, že v případě chyby metoda skončí a výjimka se takzvaně „deleguje“ nadřazenému kódu. | ||
+ | |||
+ | <div class="Priklad"> | ||
+ | ; Například | ||
+ | * Píšeme knihovní funkci, která bude sloužit pro načítání XML souborů v různých aplikacích. | ||
+ | * Přitom ale nevíme, jaká aplikace to bude: | ||
+ | ** některé aplikace mohou být textové, | ||
+ | ** jiné mohou mít grafické uživatelské rozhraní, | ||
+ | ** a některé aplikace mohou být třeba napsány v Greenfootu. | ||
+ | * Každá aplikace tedy bude chtít zareagovat na chybu jinak: | ||
+ | ** v textovém režimu vypsáním hlášení pomocí <code>System.err.println()</code>, | ||
+ | ** v grafickém režimu pomocí vyskakovacího okna | ||
+ | ** a v Greenfootu třeba tak, že nějakému aktérovi nastavíme text s popisem hlášení. | ||
+ | * Chybu tedy nechceme ošetřovat hned, protože ještě nevíme jak to nejlépe provést. | ||
+ | </div> | ||
+ | |||
+ | private void nactiSoubor() '''throws''' IOException { | ||
+ | ''... kód, kde může nastat chyba ''IOException'', na kterou ale v tomto místě neumíme správně zareagovat...'' | ||
+ | ''... další kód, který se v případě chyby přeskočí...'' | ||
+ | } | ||
+ | |||
+ | public void zpracuj() { | ||
+ | '''try''' { | ||
+ | |||
+ | ''... nějaký kód... '' | ||
+ | |||
+ | nactiSoubor(); | ||
+ | |||
+ | ''... nějaký kód - pokud v metodě ''nactiSoubor()'' nastane chyba, tento kód se přeskočí... '' | ||
+ | |||
+ | } '''catch''' (IOException ex) { ''... jak reagovat na chybu...'' | ||
+ | } | ||
+ | } | ||
+ | |||
+ | |||
+ | |||
+ | == Vyhození vlastní výjimky <code>throw</code> == | ||
+ | * Můžeme také chtít sami za nějakých okolností vytvořit výjimku. | ||
+ | * To provedeme pomocí klíčového slova <code>'''throw'''</code>, za kterým následuje kód pro vytvoření instance některé výjimky. | ||
+ | |||
+ | if (castka > limit) { | ||
+ | int rozdil = castka - limit; | ||
+ | '''throw''' new MyException("Překročen limit o: "+rozdil+" Kč"); | ||
+ | } | ||
+ | |||
+ | |||
+ | == Proč používat výjimky? == | ||
+ | |||
+ | # Datové třídy můžeme používat v různých typech aplikací (mobilní aplikace, webové aplikace, grafické uživatelské rozhraní, příkazový řádek). Chyby se ale ošetřují pokaždé jinak. Je tedy vhodné nechat (''delegovat'') zpracování do místa v kódu, kde umím chybu řešit. | ||
+ | # Chybové hlášení píšeme v místě, kde chyba nastala. Máme proto přístup k veškerým informacím tak, abychom mohli chybu podrobně popsat. | ||
+ | # Je běžné, že voláme několik vnořených metod. Při použití výjimek nemusím testovat, zda kód proběhl v pořádku a kód tak bude přehlednější. Srovnej: | ||
+ | |||
+ | ; Bez použití výjimek (červený kód řeší chybu) | ||
+ | public class Uzivatel { | ||
+ | private LocalDate datumNarozeni; | ||
+ | private <span style="color:red">boolean</span> kontrolujDatumNarozeni(LocalDate datumNarozeni) { | ||
+ | <span style="color:red">return LocalDate.now().isAfter(datumNarozeni);</span> | ||
+ | } | ||
+ | public <span style="color:red">boolean</span> setDatumNarozeni(LocalDate datumNarozeni) { | ||
+ | <span style="color:red">if (! kontrolujDatumNarozeni) return false;</span> | ||
+ | this.datumNarozeni = datumNarozeni; | ||
+ | <span style="color:red">return true;</span> | ||
+ | <span style="color:gray">// Tato metoda musí předávat informaci o chybě, je tedy zbytečně složitá.</span> | ||
+ | } | ||
+ | } | ||
+ | public class Main { | ||
+ | public static void main(String[] args) { | ||
+ | Uzivatel uzivatel = new Uzivatel(); | ||
+ | <span style="color:red">if (</span>uzivatel.setDatumNarozeni(LocalDate.of(2022,11,08))<span style="color:red">) { | ||
+ | System.err.println("Nastala chyba při nastavení data narození!"); | ||
+ | }</span> | ||
+ | } | ||
+ | } | ||
+ | |||
+ | ; S použitím výjimek (červený a oranžový kód řeší chybu, oranžový kód lze generovat automaticky) | ||
+ | Při použití výjimek je zpracování chyby z největší části koncentrováno do místa, kde chybu zjistíme. Dále už se jen předává výjimka a nakonec se zobrazí chybové hlášení | ||
+ | public class Uzivatel { | ||
+ | private LocalDate datumNarozeni; | ||
+ | private void kontrolujDatumNarozeni(LocalDate datumNarozeni) <span style="color:orange">throws Exception</span> { | ||
+ | LocalDate dnes = LocalDate.now(); | ||
+ | <span style="color:red">if (dnes.isBefore(datumNarozeni)) throw new Exception("Datum narození nesmí být v budoucnosti (dnes je "&dnes&", datum narození: "&datumNarozeni&")");</span> | ||
+ | } | ||
+ | public void setDatumNarozeni(LocalDate datumNarozeni) <span style="color:orange">throws Exception</span> { | ||
+ | kontrolujDatumNarozeni(datumNarozeni); | ||
+ | this.datumNarozeni = datumNarozeni; | ||
+ | <span style="color:gray">// Tato metoda nemusí být „zaneřáděna“ kódem, řešícím výjimku.</span> | ||
+ | } | ||
+ | } | ||
+ | public class Main { | ||
+ | public static void main(String[] args) { | ||
+ | Uzivatel uzivatel = new Uzivatel(); | ||
+ | <span style="color:orange">try {</span> | ||
+ | uzivatel.setDatumNarozeni(LocalDate.of(2022,11,08)); | ||
+ | <span style="color:orange">} catch(Exception ex) {</span> | ||
+ | <span style="color:red">System.err.println(ex.getMessage);</span> | ||
+ | <span style="color:orange">}</span> | ||
+ | } | ||
+ | } | ||
== Související stránky == | == Související stránky == |
Aktuální verze z 8. 11. 2022, 11:14
Obsah |
Co jsou výjimky?
- V každém programu mohou za určitých okolností nastávat chyby, které programátor může předvídat, ale kterým nelze obecně zabránit.
- Typicky takové chyby nastávají:
- při práci se soubory (chybějící soubor, poškozené médium, odpojené médium, uživatel špatně zadal název souboru,...)
- při práci se sítí (nelze se připojit, připojení bylo přerušeno v průběhu komunikace,...)
- při zpracovávání vstupu od uživatele (například očekáváme celé číslo, ale uživatel zadá do vstupního pole desetinné číslo)
- Pokud bychom museli psát podmínky a testovat chyby všude, kde může potenciálně nastat problém, kód aplikace by se stal brzy nepřehledným.
- Většina moderních programovacích jazyků proto zavádí koncept tzv. výjimek.
- Zjednodušeně to funguje tak, že označíme blok, kde může nastat chyba, a ošetření chyby (správná reakce na chybu) uvedeme až na konec tohoto bloku.
Ještě jednou pro ujasnění:
- Neřešíme teď situaci, kdy je aplikace špatně naprogramovaná!
- Aplikace je správně napsaná, ale problém nastane v něčem, co programátor nemůže dopředu ovlivnit.
- Například výpadek sítě při komunikaci přes síť. Takové chyby lze předvídat a můžeme do aplikace zabudovat vhodnou reakci na takovou chybu. Ale nelze zajistit, aby taková chyba nenastala.
Jak výjimky fungují?
- Pokud víme, že v nějaké části kódu aplikace může nastat chyba, kterou lze předvídat, ale nelze ji vyloučit, potom:
- Označíme blok kódu, kde chyba může nastat:
try {
...}
- Následně zapíšeme kód bez toho, abychom museli neustále testovat chyby
- Kód tedy může tedy zůstat přehledný a stručný, bez spousty podmínek.
- Pokud chyba nastane, zbytek bloku kódu se přeskočí a ihned se provede reakce na chybu (viz další krok).
- Poté popíšeme, jak zareagovat na jednotlivé typy chyb (jak zareagovat na výjimky):
catch (TypVýjimky názevVýjimky) {
... jak zareagovat ...}
- Počet typů chyb, na které reagujeme, není omezený.
- A nakonec můžeme (ale nemusíme) uvést kód, který se má provést bez ohledu na to, jestli nastala chyba:
finally {
... Co se má stát vždy na konci bloku ...}
Šablona kódu ošetření výjimek
try { // Začni provádět kód, ve kterém může nastat chybový stav... ... zde následuje kód, pracující se souborem... } catch (TypVýjimky1 ex) { // Co se má stát, pokud nastane chyba TypVýjimky1... } catch (TypVýjimky2 ex) { // Co se má stát, pokud nastane chyba TypVýjimky2... // Takto můžeme popsat reakci na různé typy výjimek. } finally { // Co se má stát každopádně po skončení bloku... // Provede se i tehdy, kdy nastane neočekávaná chyba. // Část finally není povinná, může se vynechat... }
Příklad — práce se souborem
- Předpokládejme, že máme soubor, kde jsou na každém řádku údaje o jednom pozemku.
- Na každém řádku vždy uchováváme:
- šířku pozemku (celé číslo)
- délku pozemku (celé číslo)
- popis pozemku (text)
- Položky jsou na řádku odděleny středníkem.
- Budeme postupně načítat jednotlivé řádky souboru a vypisovat je na obrazovku.
- Pokud nastane chyba, načítání se přeruší a aplikace vypíše vhodné chybové hlášení.
- Nakonec se vždy pokusíme soubor uzavřít. I v průběhu uzavírání souboru ale může dojít k chybě.
import java.util.Scanner; import java.io.*; import java.util.InputMismatchException;
...
String nazev = "vstup.txt"; int cislo = 0; Scanner sc = null; try { sc = new Scanner(new File(nazev), "windows-1250"); sc.useDelimiter("\\s*,\\s*|\\s*\r\n"); // Nastaví jako oddělovač položek čárku či konec řádku, mezery a tabulátory kolem čárky ignoruje. while (sc.hasNext()) { cislo = sc.nextInt(); int sirka = cislo; cislo = sc.nextInt(); int delka = cislo; String popis = sc.next(); System.out.println("Pozemek: "+popis+" má šířku: "+sirka+" m a délku: "+delka+" m."); System.out.println("Plocha pozemku je: "+(sirka*delka)+" m^2."); System.out.println("---------------"); } } catch (FileNotFoundException ex) { System.err.println("Nenalezen soubor: "+nazev+"!"); } catch (IOException ex) { System.err.println("Chyba čtení ze souboru "+nazev+": "+ex.getLocalizedMessage()); } catch (InputMismatchException ex) { System.err.println("Nesprávný formát čísla v souboru "+nazev+"! Poslední správně načtené číslo je: "+cislo+"!"); } finally { sc.close(); }
...
Čtení textových souborů pomocí třídy Scanner
je vhodný pouze pro velmi jednoduché textové soubory!
Pokud chcete vážně pracovat s CSV soubory, použijte některou z dostupných knihoven! Například: Univocity CSV parser!
Delegování chyb throws
- Může se také stát, že máme metodu, ve které nastává chyba, ale my nevíme, jak ji ošetřit (jak na ni správně zareagovat).
- V takovém případě můžeme použít klíčové slovo
throws
, kterým zařídíme, že v případě chyby metoda skončí a výjimka se takzvaně „deleguje“ nadřazenému kódu.
- Například
- Píšeme knihovní funkci, která bude sloužit pro načítání XML souborů v různých aplikacích.
- Přitom ale nevíme, jaká aplikace to bude:
- některé aplikace mohou být textové,
- jiné mohou mít grafické uživatelské rozhraní,
- a některé aplikace mohou být třeba napsány v Greenfootu.
- Každá aplikace tedy bude chtít zareagovat na chybu jinak:
- v textovém režimu vypsáním hlášení pomocí
System.err.println()
, - v grafickém režimu pomocí vyskakovacího okna
- a v Greenfootu třeba tak, že nějakému aktérovi nastavíme text s popisem hlášení.
- v textovém režimu vypsáním hlášení pomocí
- Chybu tedy nechceme ošetřovat hned, protože ještě nevíme jak to nejlépe provést.
private void nactiSoubor() throws IOException { ... kód, kde může nastat chyba IOException, na kterou ale v tomto místě neumíme správně zareagovat... ... další kód, který se v případě chyby přeskočí... }
public void zpracuj() { try { ... nějaký kód... nactiSoubor(); ... nějaký kód - pokud v metodě nactiSoubor() nastane chyba, tento kód se přeskočí... } catch (IOException ex) { ... jak reagovat na chybu... } }
Vyhození vlastní výjimky throw
- Můžeme také chtít sami za nějakých okolností vytvořit výjimku.
- To provedeme pomocí klíčového slova
throw
, za kterým následuje kód pro vytvoření instance některé výjimky.
if (castka > limit) { int rozdil = castka - limit; throw new MyException("Překročen limit o: "+rozdil+" Kč"); }
Proč používat výjimky?
- Datové třídy můžeme používat v různých typech aplikací (mobilní aplikace, webové aplikace, grafické uživatelské rozhraní, příkazový řádek). Chyby se ale ošetřují pokaždé jinak. Je tedy vhodné nechat (delegovat) zpracování do místa v kódu, kde umím chybu řešit.
- Chybové hlášení píšeme v místě, kde chyba nastala. Máme proto přístup k veškerým informacím tak, abychom mohli chybu podrobně popsat.
- Je běžné, že voláme několik vnořených metod. Při použití výjimek nemusím testovat, zda kód proběhl v pořádku a kód tak bude přehlednější. Srovnej:
- Bez použití výjimek (červený kód řeší chybu)
public class Uzivatel { private LocalDate datumNarozeni; private boolean kontrolujDatumNarozeni(LocalDate datumNarozeni) { return LocalDate.now().isAfter(datumNarozeni); } public boolean setDatumNarozeni(LocalDate datumNarozeni) { if (! kontrolujDatumNarozeni) return false; this.datumNarozeni = datumNarozeni; return true; // Tato metoda musí předávat informaci o chybě, je tedy zbytečně složitá. } } public class Main { public static void main(String[] args) { Uzivatel uzivatel = new Uzivatel(); if (uzivatel.setDatumNarozeni(LocalDate.of(2022,11,08))) { System.err.println("Nastala chyba při nastavení data narození!"); } } }
- S použitím výjimek (červený a oranžový kód řeší chybu, oranžový kód lze generovat automaticky)
Při použití výjimek je zpracování chyby z největší části koncentrováno do místa, kde chybu zjistíme. Dále už se jen předává výjimka a nakonec se zobrazí chybové hlášení
public class Uzivatel { private LocalDate datumNarozeni; private void kontrolujDatumNarozeni(LocalDate datumNarozeni) throws Exception { LocalDate dnes = LocalDate.now(); if (dnes.isBefore(datumNarozeni)) throw new Exception("Datum narození nesmí být v budoucnosti (dnes je "&dnes&", datum narození: "&datumNarozeni&")"); } public void setDatumNarozeni(LocalDate datumNarozeni) throws Exception { kontrolujDatumNarozeni(datumNarozeni); this.datumNarozeni = datumNarozeni; // Tato metoda nemusí být „zaneřáděna“ kódem, řešícím výjimku. } } public class Main { public static void main(String[] args) { Uzivatel uzivatel = new Uzivatel(); try { uzivatel.setDatumNarozeni(LocalDate.of(2022,11,08)); } catch(Exception ex) { System.err.println(ex.getMessage); } } }
Související stránky