Výjimky v Javě
Z MiS
				
				
				(Rozdíly mezi verzemi)
				
																
				
				
								
				| m (Oprava kódu příkladu.) |  (→Proč používat výjimky?:  Přidáno throws do kódu, obarvení kódu, který řeší výjimku.) | ||
| (Nejsou zobrazeny 2 mezilehlé verze od 1 uživatele.) | |||
| Řádka 85: | Řádka 85: | ||
|       while (sc.hasNext()) { |       while (sc.hasNext()) { | ||
|           cislo = sc.nextInt(); |           cislo = sc.nextInt(); | ||
| − |           int sirka =  | + |           int sirka = cislo; | 
|           cislo = sc.nextInt(); |           cislo = sc.nextInt(); | ||
| − |           int delka =  | + |           int delka = cislo; | 
|           String popis = sc.next(); |           String popis = sc.next(); | ||
|           System.out.println("Pozemek: "+popis+" má šířku: "+sirka+" m a délku: "+delka+" m."); |           System.out.println("Pozemek: "+popis+" má šířku: "+sirka+" m a délku: "+delka+" m."); | ||
| Řádka 158: | Řádka 158: | ||
| + | == 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
