Výjimky v Javě

Z MiS
(Rozdíly mezi verzemi)
Přejít na: navigace, hledání
(Založení stránky.)
 
(Proč používat výjimky?: Přidáno throws do kódu, obarvení kódu, který řeší výjimku.)
 
(Není zobrazeno 7 mezilehlých verzí od 1 uživatele.)
Řádka 1: Řádka 1:
 
[[Category:VSE]][[Category:Informatika]][[Category:Java]]
 
[[Category:VSE]][[Category:Informatika]][[Category:Java]]
  
... Text stránky bude doplněn...
+
 
 +
== 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.
 +
 
 +
<div class="Poznamka">
 +
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&nbsp;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&nbsp;můžeme do aplikace zabudovat vhodnou reakci na takovou chybu. Ale '''nelze zajistit, aby taková chyba nenastala.'''
 +
</div>
 +
 
 +
 
 +
 
 +
== Jak výjimky fungují? ==
 +
* Pokud víme, že v&nbsp;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:<br /><code>try { </code> ... <code> } </code>
 +
# 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&nbsp;stručný, bez spousty podmínek.
 +
#* Pokud chyba nastane, zbytek bloku kódu se přeskočí a&nbsp;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>
 +
#* Počet typů chyb, na které reagujeme, není omezený.
 +
# A&nbsp;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...''
 +
 +
    ''... 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 &mdash; práce se souborem ==
 +
* Předpokládejme, že máme soubor, kde jsou na každém řádku údaje o&nbsp;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&nbsp;v&nbsp;průběhu uzavírání souboru ale může dojít k&nbsp;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&nbsp;CSV soubory, použijte některou z&nbsp;dostupných knihoven! Například: Univocity CSV parser!
 +
</div>
 +
 
 +
 
 +
 
 +
== Delegování chyb &nbsp; <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&nbsp;případě chyby metoda skončí a&nbsp;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&nbsp;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&nbsp;některé aplikace mohou být třeba napsány v&nbsp;Greenfootu.
 +
* Každá aplikace tedy bude chtít zareagovat na chybu jinak:
 +
** v&nbsp;textovém režimu vypsáním hlášení pomocí <code>System.err.println()</code>,
 +
** v&nbsp;grafickém režimu pomocí vyskakovacího okna
 +
** a&nbsp;v&nbsp;Greenfootu třeba tak, že nějakému aktérovi nastavíme text s&nbsp;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&nbsp;tomto místě neumíme správně zareagovat...''
 +
    ''... další kód, který se v&nbsp;případě chyby přeskočí...''
 +
}
 +
 
 +
public void zpracuj() {
 +
    '''try''' {
 +
 +
        ''... nějaký kód... ''
 +
 +
        nactiSoubor();
 +
 +
        ''... nějaký kód - pokud v&nbsp;metodě ''nactiSoubor()'' nastane chyba, tento kód se přeskočí... ''
 +
 +
    } '''catch''' (IOException ex) { ''... jak reagovat na chybu...''
 +
    }
 +
}
 +
 
 +
 
 +
 
 +
== Vyhození vlastní výjimky &nbsp; <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&nbsp;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&nbsp;kódu, kde umím chybu řešit.
 +
# Chybové hlášení píšeme v&nbsp;místě, kde chyba nastala. Máme proto přístup k&nbsp;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&nbsp;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&nbsp;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&nbsp;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 ==
 
* [[Práce se soubory v Javě]]
 
* [[Práce se soubory v Javě]]
 
* [[Java: Textový vstup a výstup]]
 
* [[Java: Textový vstup a výstup]]
 +
 +
  
 
== Zdroje ==
 
== Zdroje ==
 
* [https://www.tutorialspoint.com/java/java_exceptions.htm TutorialsPoint.com &rarr; Java Exceptions]
 
* [https://www.tutorialspoint.com/java/java_exceptions.htm TutorialsPoint.com &rarr; Java Exceptions]
 +
* [https://docs.oracle.com/javase/tutorial/essential/exceptions/index.html Java Documentation &rarr; Exceptions]

Aktuální verze z 8. 11. 2022, 11:14


Obsah

Co jsou výjimky?

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í?

  1. Označíme blok kódu, kde chyba může nastat:
    try { ... }
  2. 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).
  3. 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ý.
  4. 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

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

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í.
  • 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

if (castka > limit) {
    int rozdil = castka - limit;
    throw new MyException("Překročen limit o: "+rozdil+" Kč");
}


Proč používat výjimky?

  1. 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.
  2. 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.
  3. 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


Zdroje

Osobní nástroje
Jmenné prostory
Varianty
Akce
Výuka
Navigace
Nástroje