14 KiB
14 KiB
Výjimky
- Mechanizmus výjimek je silným bezpečnostním mechanizmem nejen v Javě, ale i v mnoha jiných jazycích
- Umožňuje (a v některých případech vynucuje) řešení chybových stavů, které za běhu programu mohou z různých důvodů nastat
Základní pojmy
- Výjimka (exception)
- Též výjimečný stav (exceptional event) či (nepřesně) chyba (error)
- Událost, u které si nepřejeme, aby v programu vznikla
- Např. očekáváme od uživatele jako vstup celé číslo, ale on zadá reálné
- Bez výjimky by program fungoval lépe – podle očekávání
- Vyhození výjimky (throw exception)
- Okamžik, kdy výjimka vznikne
Popis výjimky
- Výjimka v Javě je instance konkrétní speciální třídy
- Samotná třída výjimky udává, k jaké chybě došlo
- Obsahuje upřesňující informace
- Přesnější popis chyby
- Obsah zásobníku (stack trace)
Druhy výjimek
- Existují dva základní druhy výjimek
- Exception
- Je NUTNÉ je ošetřit ve zdrojovém kódu
- RuntimeException
- Je MOŽNÉ je ošetřit ve zdrojovém kódu, ale není to nutné
- Způsob práce s oběma typy je zcela stejný
- Exception
- Od základních typů odvozeno (odděděno) mnoho dalších výjimek, které jsou více specifické
- Výjimky mají hierarchickou strukturu, na vrcholu je
Exception
- Protože všechny jsou původně odvozené (odděděné) od základních, je možné při ošetřování výjimek používat názvy nadřazených nebo základních druhů
- Např.
FileNotFoundException
(soubor nenalezen) je možné nahraditIOException
(obecná chyba vstupu a výstupu) nebo rovnouException
- Např.
- Výjimky mají hierarchickou strukturu, na vrcholu je
Výjimky (Exception)
- Též kontrolované výjimky (checked exceptions)
- Všechny tyto výjimky jsou odvozené (přímo nebo přes delší hierarchii) od
Exception
a nikde v hierarchii odvození (dědění) se nevyskytujeRuntimeException
- Je nutné se o ně v programu postarat, tj. ošetřit je přidáním speciální části zdrojového kódu
- Vyskytují se v souvislosti s voláním metod, u kterých je zvýšená pravděpodobnost, že se při jejich použití může vyskytnout problém
- Typicky metody, které pracují se vstupy a výstupy
- Tyto metody jsou napsány tak, že při jejich použití je nutné na potenciální nebezpečí reagovat
- Pokud není přidána část kódu pro ošetření případné výjimky, program nejde ani přeložit
Výjimky za běhu (RuntimeException)
- Všechny tyto výjimky jsou odvozeny (přímo nebo přes delší hierarchii) od
RuntimeException
- Není nutné se o ně v programu postarat, protože mohou nastat kdekoliv
- Příkladem je přístup do neexistujícího prvku pole (
ArrayIndexOutOfBoundException
), volání metody nad referenční proměnnou, která jenull
(NullPointerException
), dělení nulou (ArithmeticException
) a mnoho dalších - Program lze přeložit – překladač nás nenutí, abychom je ošetřili pomocí speciální části zdrojového kódu
- Považujeme-li to za vhodné, můžeme je ošetřit (stejným způsobem jako kontrolované výjimky)
- Typicky je to v místech, kde je zvýšená pravděpodobnost problému (např. vstupy od uživatele)
- Nemá větší smysl ošetřovat tyto výjimky v místě, kde jsme si stoprocentně jisti, že výjimka nemůže nastat (nemůže být vyhozena)
- Přesto se může stát, že výjimka v tomto místě nastane, protože jsme nezvážili všechny možnosti
- Pokud je výjimka za běhu programu vyhozena a není ošetřena, dojde k předčasnému ukončení programu a výpisu výjimky na standardní chybový výstup (na obrazovku)
- Z chybového výpisu lze poznat, kde k výjimce došlo
- Dalším krokem je oprava programu – odstranění důvodu výjimky nebo její ošetření
- Příkladem je přístup do neexistujícího prvku pole (
Reakce na výjimku
- Existují tři základní přístupy, jak reagovat na výjimku
- Předání výjimky výše
- Programátor výjimku nechce (nebo neumí) ošetřovat v místě vyhození, a proto informaci o jejím výskytu předá do nadřazené úrovně (tzn. do volající metody)
- Kompletní ošetření výjimky
- Výjimka je zachycena a ošetřena v místě vyhození (vzniku)
- Kombinace obou přístupů
- Výjimka je zachycena a (částečně či kompletně) ošetřena v místě vyhození, ale informace o ní se navíc předá do nadřazené úrovně (tzn. do volající metody)
- Předání výjimky výše
- Všechny tyto postupy se běžně používají
- Často se využívá nejprve předání výjimky výše a následně její ošetření ve vyšší úrovni
Předání výjimky výše (deklarace výjimky)
- Metoda, ve které se výjimka vyskytla (byla vyhozena), se zříká odpovědnosti za její zpracování
- Předání odpovědnosti výše se deklaruje v hlavičce metody
- Tento postup je velice jednoduchý, ale pouze odsouvá řešení problému, které bude muset být nakonec provedeno (ale toto řešení může být např. provedeno na vhodnějším místě, než je metoda, kde výjimka vznikla)
- Na první pohled nejjednodušší řešení
- Není třeba žádných zásahů do těla metody
- Do hlavičky metody se přidá
throws NázevVýjimky
- Např.
throws IOException
- Např.
Kompletní ošetření výjimky
- Případná vyhozená výjimka nepronikne ven z metody
- Nadřazená úroveň (volající metoda) se nemusí o nic starat
- Ošetření výjimky se provede pomocí konstrukce
try-catch-finally
- Vlastní výkonný kód se nemění, jen se uzavře do bloku začínajícího klíčovým slovem
try
- Prakticky je často nutné přesunout některé deklarace vně bloku
try
- V tomto bloku předpokládáme vznik výjimky
- Výjimka vždy vznikne na konkrétní řádce => touto řádkou skončí vykonávání bloku
try
, pokud k výjimce dojde
- Prakticky je často nutné přesunout některé deklarace vně bloku
- Blok
catch
, který za blokemtry
bezprostředně následuje, určuje, na jakou výjimku se bude reagovat a jak- Kód v bloku
catch
se provede pouze v případě, že k uvedené výjimce (někde v blokutry
) dojde - Bloků
catch
může být více, každý může reagovat na jinou výjimku - Blok
catch
reaguje pouze na výjimku, která je deklarovaná v závorce a všechny výjimky od ní odvozené (potomky)- Pokud nastane jiná výjimka, blok
catch
se neprovede - Pokud není uveden žádný vhodný blok
catch
, výjimka není odchycena
- Pokud nastane jiná výjimka, blok
- Kód v bloku
- Blok
finally
- Blok
finally
následuje za blokem catch - Obsahuje část kódu, která se provede vždy – ať už k výjimce dojde, nebo ne (i když dojde k jiné výjimce, která není odchycena)
- Nepoužívá se příliš často
- Blok
- Vlastní výkonný kód se nemění, jen se uzavře do bloku začínajícího klíčovým slovem
- Možné ošetření výjimky – pouze vypsání chyby a ukončení programu
- Primitivní, ale často rozumný způsob
- Hodí se v případech, kdy vznik výjimky znamená, že program dál stejně nemůže smysluplně pokračovat
- Umožní program přeložit (pokud ošetřujeme kontrolovanou výjimku)
- V případě, že je výjimka vyhozena, se o ní z chybového výpisu dozvíme
- Pokud ošetřujeme výjimku přímo v místě vzniku v nějaké hluboce zanořené metodě, je to často jediný smysluplný způsob kompletního ošetření výjimky
- Vhodnější alternativa je předání výjimky výše a její ošetření na vyšší úrovni, kde se dá smysluplně reagovat
- Ošetření více různých výjimek
- Za blokem
try
může následovat více blokůcatch
, odchytávajících různé výjimky- Je tak možné reagovat různě podle toho, k jaké výjimce došlo
- Je možné reagovat i na více různých výjimek stejně
- Je možné uvést výjimku, která je společným předkem všech výjimek, na které chceme reagovat stejně – často to může být přímo
Exception
- V tom případě jsou ale odchyceny všechny výjimky (i ty, o kterých jsme neuvažovali) a to není žádoucí
- Je možné uvést více výjimek do jednoho bloku
catch
- Výjimky jsou odděleny svislítkem „|“
- Lepší postup, protože jsou zachycené pouze uvedené výjimky (a výjimky od nich odvozené), ale žádné další
- Je možné uvést výjimku, která je společným předkem všech výjimek, na které chceme reagovat stejně – často to může být přímo
- Za blokem
Předání výjimky výše a její ošetření výše
- Běžný postup
- Výjimka je z metod na nižší úrovni (např. zajišťující čtení ze souboru) propagována výše, až do úrovně, kde je vhodné ji ošetřit, a tam je výjimka vhodně ošetřena (např. v GUI zobrazením dialogu s varováním)
Nejhorší reakce na výjimku
- Důvod je v naprosté většině případů lenost programátora
- Nechce výjimku propagovat, protože by to následně musel udělat i ve všech volajících metodách
- Použije se konstrukce
try-catch
, ale do blokucatch
se nic nenapíše
- Zmíněným postupem jsme se připravili o všechny výhody mechanizmu výjimek a vlastně jsme situaci ještě zhoršili
- Výjimka proběhne, ale nikdo se nedozví kdy a kde
- Někdy je však skutečně potřeba na zachycenou výjimku nijak nereagovat a také ji nepropagovat
- Typickým příkladem je odchycení
InterruptedException
při synchronizaci vláken (viz předmět KIV/PGS) - V tom případě je možné nechat blok
catch
bez výkonného kódu, ale je nutné doplnit dostatečně podrobný vysvětlující komentář, proč tomu tak je - Je také možné převést kontrolovanou výjimku na výjimku za běhu
- Typickým příkladem je odchycení
Konstrukce try-with-resources
- Po skončení práce se soubory (čtení/zápis) je obecně dobrým zvykem soubor zavřít metodou
close()
- V předchozích příkladech nebylo použito
- Protože to nebylo z hlediska výkladu podstatné a bude to podrobněji zmíněno později
- Protože neuzavřením souboru
data.txt
se ve skutečnosti nic špatného nestane- Byl otevřen pouze pro čtení
- JVM při ukončení programu (ať už s výjimkou nebo bez) automaticky uzavře všechny soubory, které program otevřel
- Neuzavření souboru však může vadit např. při zápisu do něj, a proto je rozumné soubory uzavírat vždy
- V předchozích příkladech nebylo použito
- Čtení nebo zápis do souboru může často skončit výjimkou
- Pokud se vyskytne výjimka, program neprovede část kódu uvedenou za řádkou, kde výjimka nastala
- Buď okamžitě ukončí metodu (pokud je výjimka předána výše) nebo skočí do odpovídajícího bloku
catch
- V této části kódu (která se při vyhození výjimky neprovede) se typicky vyskytuje volání metody
close()
pro uzavření souborů, které se tím pádem při výskytu výjimky neprovede- Řešení
- Přesunout volání
close()
do blokufinally
- Běžný postup, ale metoda
close()
může také vyhodit výjimku a tu je třeba ošetřit
- Běžný postup, ale metoda
- Využít konstrukci
try
-with-resources
- Přesunout volání
- Řešení
- Buď okamžitě ukončí metodu (pokud je výjimka předána výše) nebo skočí do odpovídajícího bloku
- Konstrukce
try
-with-resources- Umožňuje automatické uzavření souboru, bez ohledu na to, zda k výjimce došlo nebo ne
- Instance použitá pro práci se souborem (např. instance třídy
Scanner
) se uvede do kulatých závorek za klíčové slovotry
- Třída musí implementovat rozhraní
AutoCloseable
- Splňují knihovní třídy pro práci se soubory
- V závorce za klíčovým slovem
try
může být uvedeno i více instancí různých tříd, jednotlivé deklarace jsou odděleny středníkem
Ruční vyhození výjimky
- Výjimka může v programu nejen vzniknout, může být i vyhozena ručně
- Příkaz
throw výjimka;
- POZOR! – Nikoliv
throws
- POZOR! – Nikoliv
- Např.
throw new IOException("Soubor je prazdny.");
- Vytvořena a vyhozena nová instance výjimky
IOException
- Za příkazem
throw
je normální vytvoření instance výjimky jedním z jejích konstruktorů
- Vytvořena a vyhozena nová instance výjimky
- Např.
throw ex;
- Vyhozena existující instance výjimky, na kterou ukazuje referenční proměnná
ex
- Vyhozena může být kontrolovaná výjimka (
Exception
) i výjimka za běhu (RuntimeException
) - Ruční vyhození výjimky se používá poměrně často
- Při zadání nesprávných parametrů metody
- Při kombinaci ošetření výjimky v místě výskytu a předání výjimky výše
- Vyhozena existující instance výjimky, na kterou ukazuje referenční proměnná
- Příkaz
Vyhození výjimky při zadání nesprávných parametrů metody
- Způsob, jak zareagovat např. v setru při zadání neplatné hodnoty atributu
- Dosud jsme řešili nevhodně tím, že se v případě zadání neplatné hodnoty nic nestalo
- Výjimka je vyhozena příkazem
throw
- Pokud se jedná o kontrolovanou výjimku (
Exception
), musí být v hlavičce metody uvedena za klíčovým slovemthrows
- Pokud se jedná o výjimku za běhu (
RuntimeException
), nemusí být v hlavičce metody uvedena- Přesto je dobré ji tam uvést
- Dá se tak jasně najevo, jakou výjimku či výjimky může metoda vyhazovat
- Pokud se jedná o výjimku za běhu, není nutné výjimku ošetřovat při volání metody, i když je uvedena v hlavičce
- Přesto je dobré ji tam uvést
- Příklad vyhození vlastní výjimky v setru ve třídě
Ctverec
- Použití třídy
Ctverec
je ve tříděVlastnostiCtverce
- Použití třídy
Ošetření výjimky v místě výskytu a její předání výše
- Výjimka je v místě výskytu (částečně nebo úplně) ošetřena, ale informace o ní je propagována výše
- Propagování výše se provede v bloku
catch
příkazemthrow
- Výjimky jsou zachyceny blokem
catch
, ve kterém se provede ošetření (zde není žádné potřeba) a výjimka se následně předá výše - Uzavření souboru (pokud je otevřen) se provede v bloku
finally
(chceme ho zavřít v každém případě, ať už výjimka vznikla nebo ne)
- Výjimky jsou zachyceny blokem
- Pokud potřebujeme v ošetření výjimky provést pouze uzavření souboru (a ne jinou akci), je možné (a lepší) použít konstrukci
try
-with-resources- Bloky
catch
anifinally
se vůbec neuvedou, ale k uzavření souboru dojde - Kvůli chybějícímu bloku
catch
se výjimka ani nezachytí a není tedy ani nutné znovu ji vyhazovat příkazemthrow
- Bloky