FAV-ZCU/KIV PPA1/15. Výjimky.md

198 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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ý
- 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é nahradit ```IOException``` (obecná chyba vstupu a výstupu) nebo rovnou ```Exception```
#### 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 nevyskytuje ```RuntimeException```
- 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á je ```null``` (```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í
### 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)
- 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```
#### 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
- Blok ```catch```, který za blokem ```try``` 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 bloku ```try```) 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
- 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
- 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ší
#### 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 bloku ``catch`` 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
#### 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
- Č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 bloku ```finally```
- Běžný postup, ale metoda ```close()``` může také vyhodit výjimku a tu je třeba ošetřit
- Využít konstrukci ```try```-with-resources
- 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é slovo ```try```
- 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```
- 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ů
- 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
#### 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 slovem ```throws```
- 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říklad vyhození vlastní výjimky v setru ve třídě ```Ctverec```
- Použití třídy ```Ctverec``` je ve třídě ```VlastnostiCtverce```
#### 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říkazem ```throw```
- 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)
- 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``` ani ```finally``` 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říkazem ```throw```