K čemu jsou výjimky (1)

  • Výjimky jsou mechanizmem umožňujícím reagovat na nestandardní (tj. chybové) běhové chování programu, které může mít různé příčiny:

    chyba okolí

    uživatele, systému

    vnitřní chyba programu

    tedy programátora.

K čemu jsou výjimky (2)

  • Mechanizmus, jak psát robustní, spolehlivé programy odolné proti chybám "okolí" i chybám v samotném programu.

  • Výjimky v Javě fungují podobně jako v C++, C#, Ruby, Pythonu a dalších zejména objektových jazycích.

  • v Javě jsou ještě lépe implementovány než v C++ (navíc klauzule finally)

  • Podobně jako jinde, ani v Javě není dobré výjimkami potlačovat očekávané chyby programu samotného — to by bylo hrubé zneužití.

Co technicky jsou výjimky

Výjimka (Exception)

objekt třídy java.lang.Exception.

  • Příbuzným typem jsou rovněž vážné běhové chyby — ty jsou objekty třídy java.lang.Error .

  • Každopádně v obou případech rozšiřuje třídu java.lang.Throwable.

  • Ta je také často používaná jako deklarativní typ pro výjimky v širším slova smyslu.

Kdy výjimka vznikne

Objekt výjimky je vyhozen (throw) buďto:

  1. automaticky běhovým systémem Javy, nastane-li nějaká běhová chyba — např. dělení nulou, nebo

  2. jsou vytvořeny a vyhozeny samotným programem, zdetekuje-li nějaký chybový stav, na nějž je třeba reagovat — např. do metody je předán špatný argument.

Kdy výjimka vznikne — příklad

try {
   // First, the exception can be raised in JVM when
   // dereferencing using a bad index in an array:
   String name = args[i]; // ArrayIndexOutOfBoundsException
   // or the exception(s) can be raised by programmer
   // in the constructor of Person
   // NullPointerException or IllegalArgumentException
   p = new Person(name);
} catch(ArrayIndexOutOfBoundsException e) {
   System.err.println("No valid person name specified");
   System.exit(1);
} catch(NullPointerException e) {
   // reaction upon NullPointerException occurence
} catch(IllegalArgumentException e) {
   // reaction upon IllegalArgumentException occurence
}

Programové bloky pro práci s výjimkami

  • Bloky programu pro práci s výjimkami:

    try

    blok vymezující místo, kde může výjimka vzniknout

    catch

    blok, který se vykoná, nastane-li výjimka odpovídající typu v catch

    finally

    blok, který se vykoná vždy, viz dále.

Co se s vyhozenou výjimkou stane

Vyhozený objekt výjimky je buďto:

  1. Zachycen v rámci metody, kde výjimka vznikla → a to v bloku catch.

    • Takto to fungovalo ve výše uvedeném příkladu — všechny výjimky byly zachyceny a řešeny svými bloky catch.

  2. Nebo výjimka propadne do nadřazené (volající) metody, kde je buďto v bloku catch zachycena nebo opět propadne atd.

    • Výjimka tedy "putuje programem" tak dlouho, než je zachycena.

    • Pokud není nikde zachycena, běh JVM skončí s hlášením o výjimce.

Syntaxe kódu s ošetřením výjimek

try {
   //zde může vzniknout výjimka
} catch (ExceptionType exceptionVariable) {
   // zde je výjimka ošetřena
   // je možné zde přistupovat k "exceptionVariable"
}
  • Bloku try se říká hlídaný blok,

  • protože výjimky příslušného typu uvedeného v hlavičce/-ách bloků catch zde vzniklé jsou zachyceny.

Reakce na výjimku — možnosti

  • Jak můžeme na vyhozenou výjimku reagovat?

    Napravit příčiny vzniku chybového stavu
    • např. znovu nechat načíst vstup nebo

    • poskytnout za chybný vstup náhradu — např. implicitní hodnotu;

    Operaci neprovést

    a sdělit chybu výše tím, že

    • výjimku propustíme z metody (propadne z ní).

    • Možnost úniku výjimky (propuštění) z metody se deklaruje pomocí throws v hlavičce metody.

Reakce na výjimku — pravidla

  • Vždy nějak reagujme, tzn. výjimku neignorujme, nepotlačujme.

  • Tudíž blok catch nenechávejme prázdný,

  • přinejmenším vypišme e.printStackTrace().

  • Nelze-li rozumně reagovat na místě, propusťme výjimku výše a popišme to v dokumentaci, viz následující příklad.

Příklad komplexní reakce na výjimku

int i = 0;
String param;
int cislo = 0;
boolean ok = false;
do {
   try {
      // zde může vzniknout výjimka ArrayIndexOutOfBoundsException
      param = args[i];
      // zde může vzniknout výjimka NumberFormatException
      cislo = Integer.parseInt(param);
      // sem se dostane, jen když nevznikla žádná výjimka
      ok = true;
   } catch (NumberFormatException nfe) {
      // sem se dostane, byl-li vstup ve špatném formátu (ne číslo)
      System.err.println("Parametr "+i+" neni platne cele cislo");
      i++; // zkusíme další parametr
   } catch (ArrayIndexOutOfBoundsException iob) {
      // sem se dostane, byl-li překročena velikost pole -> chybový výstup err
      System.err.println("Nepredan zadny ciselny parametr");
      // sami vyhodíme výjimku
      throw new IllegalArgumentException(
         "Nezadan zadny celociselny parametr.");
   }
} while (!ok);
System.out.println("Zadano cislo="+cislo);

Kaskády bloků catch

  • V některých blocích try mohou vzniknout výjimky více typů

  • pak můžeme uvádět více catch po sobě, viz přechozí příklad.

  • Pokud catch takto řetězíme, musíme respektovat, že výjimka bude zachycena nejbližším příhodným catch a

  • překladač si ohlídá, že kód neobsahuje nedosažitelné catch-bloky.

  • Pozor tedy na řetězení catch s výjimkami typů z jedné hierarchie tříd.

  • Vždy musí být výjimka z podtřídy (tj. speciálnější) uvedena a tedy zachycována dříve než výjimka obecnější.

Příklad kaskády catch špatně (1)

  • Definice vlastních výjimek — jedna podtřídou druhé

class MyException1 extends Exception {}
class MyException2 extends MyException1 {}

Příklad kaskády catch špatně (2)

  • Jejich chybné použití v kaskádě

try {
   if (args.length == 0 || args[0].equals("1"))
      throw new MyException1();
   else if (args[0].equals("2"))
      throw new MyException2();
// CHYBA: v kaskádě je nejprve zachycována obecnější výjimka MyException1
} catch (MyException1 mv) {
   System.out.println("Zachycena vyjimka typu MyException1: " + mv);
   // speciálnější výjimka MyException2 by nikdy nebyla zachycena,
   // proto překladač takovou konstrukce ani nepřeloží.
} catch (MyException2 mv) {
   System.out.println("Zachycena vyjimka typu MyException2: "+mv);
}

Výjimky a jejich hlídání překladačem

  • Java je staticky (překladově) typovaný jazyk a jako takový zná místa potenciálního vyhození výjimky.

  • V dosud uváděných příkladech se neprojevovalo, že by nás nějak hlídal.

  • Bylo to proto, že jsme dosud používali tzv. běhové (nehlídané) výjimky (runtime-/unchecked exceptions), jejichž místa vzniku překladač nesleduje a nehlídá, jak na ně reagujeme.

  • Nyní nastíníme úplnou kategorizaci výjimek vč. hlídaných.

Kategorizace výjimek a dalších chybových objektů

hlídané výjimky

checked exceptions

  • potomky/instancemi třídy java.lang.Exception

  • překladač sleduje místa jejich vzniku a povinnou reakci na ně

  • nebo jejich možné propuštění z metody ven v případě deklarace throws

běhové výjimky

také nehlídané výjimky, unchecked exceptions

  • jsou typu nebo potomky java.lang.RuntimeException

  • a nemusejí být zachytávány.

vážné chyby JVM

signalizují těžce napravitelné chyby v JVM potomky

  • instance java.lang.Error

  • např. Out Of Memory, Stack Overflow …,

  • ale též např. chybu programátora: AssertionError.

Kdy použít hlídanou a nehlídanou výjimku

Unchecked exceptions

represent defects in the program (bugs) — often invalid arguments passed to a non-private method.

Unchecked runtime exceptions represent conditions that, generally speaking, reflect errors in your program’s logic and cannot be reasonably recovered from at run time.

— Gosling Arnold and Holmes
Checked exceptions

represent invalid conditions in areas outside the immediate control of the program (invalid user input, database problems, network outages, absent files).

Metody propouštějící výjimku

  • Ne všechny hlídané výjimky se musejí v metodě vzniku zachytit pomocí catch.

  • Některé mohou být z metody propuštěny (mohou "propadnout výše").

  • Indikováno v hlavičce takové metody pomocí throws:

modifikátory návratovýTypzevMetody(argumenty)
throws TypPropouštěnéVýjimky {
   ... tělo metody, kde může výjimka vzniknout ...
}
  • Pokud daná hlídaná výjimka nikde v těle nemůže vzniknout, překladač to zdetekuje a vypíše:

Exception XXX is never thrown in YYY

Příklad s propouštěnou výjimkou

private static void openFile(String filename) throws IOException {
   System.err.println("Zkouším otevřít soubor "+filename);
   FileReader r = new FileReader(filename);
   // povedlo se!
   // děláme s ním něco dále
}
public static void main(String[] args) {
   try {
      openFile(args[0]);
      System.err.println("Soubor otevřen");
   } catch (IOException ioe) {
      System.err.println("Nelze otevřít soubor");
   }
}

Vlastní typy výjimek

  • Typy (=třídy) výjimek si můžeme definovat sami.

  • Bývá zvykem končit názvy tříd výjimek na Exception (např. OverloadedException).

Klauzule finally

  • Klauzule (blok) finally může následovat ihned po bloku try nebo až po blocích catch.

  • Slouží k "úklidu v každém případě", tj.

    • když je výjimka zachycena blokem catch,

    • i když je výjimka propuštěna do volající metody.

  • Používá se typicky pro uvolnění (systémových) zdrojů — uzavření souborů, soketů.

Příklad finally (1)

public class NotEnoughParametersException extends Exception {
   private int countParam;
   public NotEnoughParametersException(int countParam) {
      this.countParam = countParam;
   }
   // ...
}
Note
Všimněte si, že u výjimek stejně jako u jiných tříd můžeme mít atributy, konstruktory, atd.

Příklad finally (2)

try {
   if (countParam < 2)
      throw new NotEnoughParametersException(countParam);
   // sem se půjde, nenastane-li výjimka
   System.out.println("Spravný počet parametrů: "+countParam);
} catch (NotEnoughParametersException mp) {
   // sem se půjde, nastane-li výše výjimka
   System.out.println("Malo parametru: " +mp.getCountParam());
} finally {
   // sem se půjde VŽDY
   System.out.println("Konec");
}

Odkazy