Image
22.6.2016 0 Comments

Java pod lupou I. /11.časť

Späť na úvod >> Späť na programovanie >> Späť na seriál

Výnimky

V každom programe sa nachádza chyba. Možno diskutovať, či je táto stará programátorská axióma pravdivá alebo nie, rozhodne však počas behu programu dochádza k situáciám, ktoré nie v súlade s predpokladaným správaním programu. Zoberme si napríklad program, ktorý má naformátovať disketu. Takýto program môže perfektne fungovať s disketou v mechanike, ale čo v prípade, že je mechanika prázdna? Je neprijateľné, aby používateľ dostal správu typu Invalid drive specification – program namiesto neľútostného odchodu do binárnych večných lovísk musí vzniknutý chybový stav detegovať a vhodne ošetriť. Iné príklady chybových stavov v programe: nedostatok pamäte, nepripravenosť externých zariadení, pokus o delenie nulou a pod.

Výnimky v Jave

Práca s chybovými stavmi je pre programátora v Jave značne zjednodušená vďaka existencii mechanizmu tzv. výnimiek. Pojem výnimka reprezentuje akúkoľvek situáciu, ktorá narúša normálny beh programu a na ktorej výskyt treba náležite reagovať. Súčasne pod výnimkou rozumieme konkrétny objekt (teda inštanciu triedy), ktorý nesie informáciu o vzniknutej chybe.

Každá výnimka v Jave musí byť inštanciou triedy Throwable, resp. niektorého jej potomka. Táto trieda má štandardne dve podtriedy: ErrorException. Výnimky typu Error reprezentujú vážne, obyčajne nenapraviteľné chyby – vnútorná chyba virtuálneho stroja, chyba pri linkovaní tried a pod. Od programu sa neočakáva, že bude na výnimky tohto typu reagovať. Výnimky triedy Exception naproti tomu predstavujú bežné chybové situácie, ktoré rozumný program dokáže detegovať a ošetriť. Od oboch tried je, pochopiteľne, odvodená celá hierarchia ďalších, špecifických tried (napr. trieda IOException, ktorá reprezentuje chyby pri vstupno-výstupných operáciách).

Ku vzniku výnimky môže dôjsť z troch príčin:

1.    výnimka vznikne synchrónne, pri vyhodnocovaní výrazu, pri chybe počas nahrávania a linkovania triedy, alebo pri vyčerpaní dostupných zdrojov (napr. pamäte)

2.    výnimka vznikne ako dôsledok vykonania príkazu throw (pomocou ktorého môže programátor vyvolať výnimku explicitne)

3.    výnimka vznikne asynchrónne (obyčajne ako dôsledok chyby virtuálneho stroja)

Výnimky sa ďalej delia na kontrolovanénekontrolované. Každá metóda, v ktorej môže dôjsť k neošetrenej kontrolovanej výnimke, to musí explicitne oznámiť vo svojej deklarácii pomocou klauzuly throws (pozri ďalej). Nekontrolované výnimky takto deklarovať netreba – buď sa môžu vyskytnúť v ľubovoľnom mieste programu, alebo sú natoľko časté, že ich deklarácia by program zbytočne zneprehľadňovala. Medzi nekontrolované výnimky patria výnimky triedy Error a jej potomkov a aj výnimky triedy RuntimeException a jej potomkov. Trieda RuntimeException je podtriedou triedy Exception, reprezentujúcou chyby, ku ktorým môže dôjsť všeobecne počas behu programu, viac-menej nezávisle od konkrétnej operácie (príklad: výnimka NullPointerException, ktorá vzniká pri pokuse o dereferenciu hodnoty null).

Ošetrenie výnimiek

Pokiaľ v ľubovoľnom mieste programu dôjde k výnimke, okamžite sa ukončí práve vykonávaný príkaz a začne sa hľadať vhodný obslužný blok (v angl. origináli exception handler). V prípade, že sa takýto blok nenájde v aktuálnej metóde, táto metóda sa predčasne ukončí a hľadanie sa presunie do volajúcej metódy (hovoríme, že výnimka sa rozšírila do volajúcej metódy). V nej pokračuje hľadanie rovnakým spôsobom od bodu volania metódy, v ktorej výnimka vznikla. Výnimka sa postupne šíri metódami podľa spätného poradia ich volania, až kým sa nenájde vhodný handler. Ak sa stane, že sa výnimka nezachytí ani v metóde main(), program oznámi výskyt neošetrenej výnimky a predčasne sa skončí.

Na ošetrenie výskytu výnimiek slúži príkaz try, ktorého úplný tvar je nasledujúci:

try {
    strážený blok
}
catch ( form-arg )
{
    ošetrenie výnimky
}
finally
{
    finálny blok
}

Kľúčové slovo try uvádza tzv. strážený blok príkazov. Za try blokom sa nachádzajú klauzuly catch, ktoré predstavujú jednotlivé obslužné bloky výnimiek. Po kľúčovom slove catch v každom bloku nasleduje deklarácia formálneho argumentu (presne tak isto, ako je to v deklarácii metód), ktorý predstavuje zachytenú výnimku. Tento formálny argument musí byť typu Throwable alebo niektorého z jeho potomkov. Celý príkaz ukončuje blok finally.

Mechanizmus zachytenia výnimky je nasledujúci:

·      Ak v bloku try nedôjde k žiadnej výnimke, vykoná sa celý tento blok a po ňom všetky príkazy bloku finally.

·      V prípade, že sa v ktoromkoľvek bode bloku try vyskytne výnimka, preskočia sa všetky zvyšné príkazy až po koniec tohto bloku a začne sa hľadať vhodný handler. Pri hľadaní sa porovnáva skutočný typ vyhodenej výnimky s typom formálneho argumentu každej klauzuly catch. Výnimku dokáže zachytiť prvý handler, ktorého typ formálneho argumentu je zhodný s typom výnimky alebo je mu nadradený (v zmysle dedičnosti; platí tu ostatne rovnaké pravidlo – potomok môže vždy zastúpiť predka).

·      Ak sa vhodný handler nájde, inicializuje sa jeho formálny argument inštanciou výnimky, vykoná sa telo handlera a po ňom sa celý príkaz dokončí vykonaním bloku finally.

·      V prípade, že ani jeden prítomný handler nevyhovuje, výnimka sa rozšíri „o poschodie vyššie“. No prv, než dôjde k opusteniu aktuálnej metódy, vykonajú sa príkazy bloku finally.

Ako vidno, blok finally sa vykoná za každých okolností, preto doň obyčajne dávame „upratovacie“ príkazy, ktoré uvoľnia alokované prostriedky a vrátia veci do konzistentného stavu.

Na pamäti treba mať fakt, že v prípade výskytu neošetrenej výnimky sa blok try nedokončí a celý zvyšok metódy sa preskočí, pretože dôjde k nelokálnemu skoku do volajúcej metódy. A ak volajúca metóda výnimku nedokáže zachytiť, ani ona sa nedokončí a výnimka pokračuje stále vyššie a vyššie. Riadenie sa programu vráti až v okamihu, keď sa nájde vhodný handler – program po spracovaní výnimky pokračuje za tým príkazom try, ku ktorému tento handler patrí.

V príkaze try sa nemusí nachádzať nijaký obslužný blok catch. V takom prípade sa výnimka vždy rozšíri do volajúcej funkcie, ale ešte predtým sa vykoná blok finally. Ak chceme blok finally vynechať (to môžeme, nie je povinný), musíme uviesť aspoň jednu klauzulu catch.

Ako sme už spomínali, metóda, ktorá vie, že sa z nej môže rozšíriť nejaká výnimka, musí to explicitne deklarovať pomocou klauzuly throws. Tá sa nachádza v deklarácii medzi ukončujúcou zátvorkou zoznamu formálnych argumentov metódy a samotným telom funkcie a pozostáva z kľúčového slova throws a čiarkou oddeleného zoznamu povolených výnimiek (resp. ich tried). Pozor však, metóda odvodenej triedy nemôže takto deklarovať viac výnimiek ako prekrytá metóda nadradenej triedy.

V zásade možno povinnosť deklarovať typ vyhadzovaných výnimiek obísť tým, že do každej metódy sa doplní deklarácia throws Throwable, čím sa postihnú všetky možné výnimky. Toto riešenie sa však neodporúča, pretože sa ním programátor dobrovoľne vzdáva jednej z možností kontroly správnosti programu.

Väčšinu preddefinovaných výnimiek vyhadzujú metódy štandardných tried Java API. Programátor si však môže definovať vlastné triedy výnimiek, odvodené obyčajne od triedy Exception. Takéto vlastné výnimky (ale nielen tie) potom môže na vhodnom mieste programu explicitne vyvolať pomocou príkazu throw. Jeho syntax je jednoduchá:

throw výraz ;

Uvedený výraz sa musí vyhodnotiť na inštanciu triedy Throwable alebo niektorej z jej potomkov. Príkaz throw je správne použitý pri splnení aspoň jednej z nasledujúcich troch podmienok:

1.    nachádza sa v bloku try, ktorý dokáže zachytiť uvedenú výnimku

2.    nachádza sa v metóde, ktorá deklaruje, že môže vyhodiť uvedenú výnimku

3.    vyhadzuje nekontrolovanú výnimku

Výrazom v príkaze throw môže byť už vopred existujúci objekt, ale najčastejšie sa príslušná inštancia výnimky vytvára až v okamihu jej vyhodenia, takže sa obyčajne stretneme so zápisom:

throw new Exception();

Príklad

Na demonštráciu práce s výnimkami poslúži nasledujúci príklad (je pomerne jednoduchý, zložitejší príklad nájde čitateľ ako vždy na webových stránkach PC REVUE):

// TestException.java
public class TestException extends Exception
{
  TestException()
  { super(); }
  TestException(String s)
  { super(s); }
}
 
// Exceptions.java
public class Exceptions
{
  public static void main(String[] args)
  {
    for (int i = 0; i < 4; i++)
    {
      try {
        throwIt(i);
        System.out.println(i + ": no exception");
      }
      catch (Exception e)
      {
        System.out.println(i + ": " + e.getClass()
                             + ": " + e.getMessage());
      }
    }
  }
  static void throwIt(int i) throws TestException
  {
    try {
      switch (i)
      {
      case 0:   i = i/i;
      case 1:   String s = null;
                i = s.length();
      case 2:   throw new TestException("just test");
      default:  return;
      }
    }
    finally
    {
      System.out.println("-- throwIt(" + i + ") done --");
    }
  }
}

Trieda TestException predstavuje našu vlastnú triedu výnimiek. Je odvodená od triedy Exception, preto patrí medzi kontrolované výnimky. Dva (zvyčajné) konštruktory len volajú konštruktory nadradenej triedy. Pri vytvorení inštancie výnimky je možné zadať pomocný, vysvetľujúci text.

Metóda main() hlavnej triedy Exceptions volá metódu throwIt() postupne s argumentom 0, 1, 23. Toto volanie je uzavreté do bloku try. Metóda throwIt() v závislosti od svojho argumentu vyvoláva rôzne typy výnimiek (prípadne aj žiadnu). Všimnime si, že pomocou klauzuly throws táto metóda deklaruje len výnimku TestException, ostatné dve (ArithmeticExceptionNullPointerException) patria medzi nekontrolované. Žiadna výnimka v metóde nie je ošetrená, takže sa z nej rozšíri von, najprv sa však vďaka bloku finally vypíše patričná správa. V metóde main() sa nachádza jediný handler, ktorý zachytáva všetky výnimky typu Exception a jeho potomkov – to znamená, že zachytí čokoľvek, čo môže „prísť“ z metódy throwIt(). Úlohou handlera je vypísať informácie o výnimke pomocou jej metód getClass() (zdedená z triedy Object – vráti triedu výnimky, ktorá sa v uvedenom výraze automaticky konvertuje na reťazec obsahujúci jej názov) a getMessage() (vracia doplnkovú informáciu o výnimke, ktorú sme v prípade triedy TestException vytvorili sami – to je ten text „just test“). Výstup programu je takýto:

-- throwIt(0) done --
0: class java.lang.ArithmeticException: / by zero
-- throwIt(1) done --
1: class java.lang.NullPointerException: null
-- throwIt(2) done --
2: class TestException: just test
-- throwIt(3) done --
3: no exception

Ako vidieť, program sa napriek výskytu výnimočných stavov neskončil chybovým hlásením, namiesto toho výnimky korektne „ošetril“.

Balík java.lang

V druhej polovici tejto časti a v niekoľkých ďalších pokračovaniach seriálu sa postupne prejdeme štandardnými balíkmi Java API a ich najčastejšie používanými triedami a rozhraniami. Nie je, pochopiteľne, možné zapodievať sa na stránkach časopisu každou triedou podrobne, čitateľ sa preto v prípade hlbšieho záujmu bude musieť obrátiť na oficiálnu dokumentáciu k JDK. Ešte upozornenie: opis balíkov je v súlade s aktuálnou verziou JDK 1.3.1 – v prípade použitia staršej verzie môžu v dokumentácii niektoré metódy dosiaľ „neexistovať“.

Prvým na rane je balík java.lang, ktorý obsahuje triedy úzko súvisiace s návrhom programovacieho jazyka Java. Veľmi dôležitou súčasťou tohto balíka je trieda Object, priamy či nepriamy predok ktorejkoľvek deklarovanej javovskej triedy. Object obsahuje niekoľko metód, na ktoré by mal reflektovať prakticky každý objekt. Metóda clone() slúži na vytvorenie plytkej kópie objektu (shallow copy, t. j. člen po člene), ale len v prípade, že príslušná trieda implementuje rozhranie Cloneable (ktoré je takisto súčasťou balíka java.lang). Toto rozhranie neobsahuje nijaké metódy, slúži len na indikáciu, že je možné klonovanie previesť. Ďalšia metóda equals() testuje zhodu objektu s ľubovoľným iným objektom. Podmienku zhody dvoch objektov si môže každá trieda definovať sama. Metóda toString() vracia textovú reprezentáciu objektu, getClass() vracia objekt triedy Class (poyri ďalej) nesúci informácie o triede objektu, ďalšia metóda hashCode() vypočítava hash hodnotu objektu, ktorú používa napríklad údajová štruktúra Hashtable. Metódy wait(), notify()notifyAll() slúžia na implementáciu synchronizácie procesov a threadov – tejto téme bude venovaná samostatná časť seriálu.

Ďalšia trieda Class je nositeľom informácií o triedach programu. Nemá verejný konštruktor, pretože pre každú zavedenú triedu, rozhranie, pole, dokonca aj pre primitívne typy sa vytvára jedna jej inštancia automaticky. Odkaz na túto inštanciu dostaneme volaním spomínanej metódy getClass() alebo pomocou statickej metódy Class.forName(), ktorej parametrom je názov triedy. Trieda Class obsahuje niekoľko metód na zisťovanie typu inštancie (ako isArray(), isInstance() a pod.) a množstvo metód s názvom v tvare get...(), pomocou ktorých sa dajú získať kompletné informácie o zložení triedy, o obsahu jej deklarácie a podobne. Ako príklad možno uviesť metódu getFields(), vracajúcu pole objektov Field opisujúcich údajové členy triedy. Trieda Field a ďalšie opisné triedy, ako Method či Constructor, sa nachádzajú v podbalíku java.lang.reflect.

Trieda String predstavuje reťazcový údajový typ. V porovnaní s jazykom C sa v Jave s reťazcami pracuje omnoho komfortnejšie, k dispozícii je množstvo metód na pohodlnú manipuláciu. Nové inštancie triedy String možno vytvoriť klasickým objektovým spôsobom:

String s = new String("Hello");

alebo aj takto (je to špeciálny prípad, iné objekty týmto spôsobom nevytvoríme):

String s = "Hello";

Nový reťazec možno vytvoriť na základe iného, už existujúceho reťazca, alebo pomocou poľa bajtov či poľa znakov. Metódy triedy String umožňujú sprístupňovať jednotlivé znaky (charAt()), porovnávať reťazce (compareTo()), spájať ich (concat()), určovať ich dĺžku (length()), hľadať jeden reťazec v druhom (indexOf(), lastIndexOf()), meniť veľkosť znakov (toLowerCase(), toUpperCase()) a iné. Dva reťazce možno okrem metódy concat() spojiť aj pomocou operátora +:

String s = "He" + "llo";

V triede String sa nachádza množstvo prekrytých verzií statickej metódy valueOf(), pomocou ktorej možno prevádzať primitívne typy na reťazce. Dá sa však použiť aj takýto zápis:

double pi = 3.14159;
String s = "pi = " + pi;

Reťazec s bude mať hodnotu "pi = 3.14159".

Inštancie triedy String predstavujú reťazce s nemennou dĺžkou. V prípade, že potrebujeme reťazec konštruovať dynamicky, pridávať k nemu či uberať z neho znaky, treba použiť triedu StringBuffer. V nej nájdeme metódy ako append(), delete(), insert(), replace(), substring() a podobne, ktorých funkciu azda ani netreba vysvetľovať.

V praxi občas nastane situácia, keď by bolo vhodné, aby sa premenná primitívneho typu správala ako objekt. Triviálnym príkladom môže byť snaha vrátiť z metódy viac ako jednu hodnotu, prípadne odovzdať primitívny argument metóde „odkazom“, a nie hodnotou, ako to v Jave štandardne je. Na tieto účely nájdeme v balíku java.lang súbor tried Boolean, Byte, Character, Double, Float, Integer, Long, ShortVoid. Uvedené triedy predstavujú objektové zapuzdrenie primitívnych typov a s výnimkou tried Boolean, CharacterVoid sú odvodené od abstraktnej triedy Number. Táto trieda deklaruje niekoľko metód s názvom v tvare xxxValue() (kde xxx je názov primitívneho typu), určených na konverziu „objektového“ čísla na iný typ. Prakticky všetky spomínané triedy sa dokážu skonštruovať aj na základe reťazca. V prípade nemožnosti prevodu generujú výnimku NumberFormatException. Medzi ďalšími metódami nájdeme compareTo() na porovnávanie zapuzdrených „čísel“, rôzne (statické) klasifikačné metódy, ako isInfinite() či isNaN() v triedach FloatDouble, a extrémne užitočné statické metódy typu parse...(), ktoré slúžia na prevod reťazca na číslo (teda nie zapuzdrený objekt) príslušného typu. Príklad takéhoto prevodu:

String s = "13.98";
double d;
try {
  d = Double.parseDouble(s);
}
catch (NumberFormatException e)
{
  d = 0.0;
}

(Výnimku NumberFormatException treba buď zachytiť, alebo deklarovať v klauzule throws obklopujúcej funkcie.) V triede Character navyše nájdeme kolekciu klasifikačných metód na zisťovanie typu znakov, ako napr. isDigit(), isLetter(), isLowerCase(), isWhitespace() a iné.

Ďalšou významnou triedou balíka java.lang je trieda Math. Nie je možné vytvárať jej inštancie, všetky jej metódy sú statické a slúžia na realizáciu najrozmanitejších matematických operácií. Ich názvy sú dostatočne opisné: abs(), acos(), asin(), atan(), cos(), exp(), log(), max(), min(), random() (tú sme už v minulosti použili!), round(), sin(), sqrt() či tan().

Skupina tried Thread, ThreadGroup, ThreadLocalInheritableThreadLocal v súčinnosti s rozhraním Runnable nájde uplatnenie pri programovaní multithreadových aplikácií. Ich použitie spolu s praktickou ukážkou spolupráce viacerých threadov príde na rad neskôr. Multithreading ostatne významne súvisí so synchronizáciou, pretože paralelne bežiace thready často súťažia o prostriedky, ako čas procesora, pamäť a podobne.

Trieda System realizuje väzbu programu na hostiteľský systém. Takisto z nej nemožno tvoriť inštancie. Trieda obsahuje tri dôležité členy in, out, err, predstavujúce štandardný vstup, štandardný výstup a štandardný chybový výstup vo forme objektových prúdov, o ktorých bude reč nabudúce. Metóda exit() slúži na ukončenie bežiaceho programu, currentTimeMillis() vracia aktuálny čas v milisekundách od polnoci 1. 1. 1970 (t. j. klasický unixový čas), getProperty()/setProperty()getProperties()/setProperties() sprístupňujú systémové nastavenia (properties; vzdialená analógia registrov či INI súborov), setSecurityManager() umožňuje aktiváciu zvolených bezpečnostných politík.

Trieda SecurityManager slúži na implementáciu bezpečnostných politík. Bližší opis, žiaľ, presahuje rámec tohto článku. Trieda Runtime sprístupňuje run-time prostredie, v ktorom program beží. Ďalšia trieda Process sa používa pri vytváraní natívnych procesov prostredníctvom metód Runtime.exec(). Inštancie triedy Package obsahujú informácie o javovských balíkoch (názov, dodávateľa, verziu a pod.).

Konečne poslednou významnou triedou balíka java.lang je v úvode článku spomínaná trieda Throwable, ktorá je rodičovskou triedou všetkých chýb a výnimiek v Jave. Trieda obsahuje okrem iného metódy getMessage()getLocalizedMessage(), vracajúce bližší opis výnimky, a metódu printStackTrace(), ktorá dokáže vypísať aktuálny stav zásobníka volaných metód v okamihu výskytu výnimky. Podtriedy ErrorException ani ich potomkovia nijaké ďalšie metódy nepridávajú – celá hierarchia výnimkových tried slúži predovšetkým na jednoznačnú identifikáciu vzniknutého chybového stavu. Ako sme už uviedli, trieda Error a jej podtriedy reprezentujú nezotaviteľné chyby, pri ktorých sa neočakáva, že ich bude aplikácia zachytávať a ošetrovať. Naproti tomu Exception a jej potomkovia indikujú výskyt bežných, ošetriteľných chýb. Z tých, s ktorými sa programátor stretne najčastejšie, možno vybrať ClassNotFoundException, oznamujúcu neprítomnosť binárneho obrazu triedy, ktorej inštanciu treba vytvoriť, InterruptedException, ktorú je potrebné ošetrovať v prípade použitia metódy wait(), ďalej IOException a jej podtriedy, ako napr. FileNotFoundException či MalformedURLException, ktoré indikujú nejakú chybu pri práci so vstupno-výstupnými triedami, ako aj množstvo potomkov triedy RuntimeException (tú, ako vieme, netreba deklarovať ani zachytávať), ako ArithmeticException, IllegalArgumentException, IndexOutOfBoundsException, NoSuchElementException či obľúbenú NullPointerException, ktorých názvy hovoria samy za seba.

Balík java.lang toho obsahuje ešte o niečo viac, ale drinu spojenú s podrobným štúdiom dokumentácie si už musí každý programátor „oddrieť“ sám. Nabudúce sa pozrieme na zúbky hlavne balíku java.io, v ktorom nájdeme triedy na realizáciu mnohých vstupno-výstupných operácií. Dovidenia o mesiac.

 

 

 


Nechajte si posielať prehľad najdôležitejších správ emailom

Mohlo by Vás zaujímať

Ako na to

Tipy a triky: Ako na snímku obrazovky na akomkoľvek počítači s Windows?

02.12.2016 00:13

Ak snímky obrazovky robíte často apotrebujete napríklad funkcie na posun stránok alebo snímanie zobrazenia pri vyššom rozlíšení displeja, zrejme používate nejakú špecializovanú aplikáciu. Väčšina použ ...

Ako na to 1

Tipy a triky: Ako aplikácii prednastaviť spúšťanie s administrátorskými právami?

30.11.2016 00:10

Väčšina aspoň trochu skúsenejších používateľov vie, že aj keď máte na operačnom systéme Windows vytvorený administrátorský účet, aplikácie pre bezpečnosť nefungujú vždy splnými administrátorskými práv ...

Ako na to 2

Tipy a triky: Ako vypnúť uzamykaciu obrazovku vo Windows 10?

29.11.2016 00:10

Rozčuľuje vás, že pred každým prihlásením doúčtu vášho počítača musíte prejsť uzamykacou obrazovkou? Windows 10 na tejto obrazovke ukazuje čas,dátum anejakú zaujímavú fotografiu zrôznych kútov sveta. ...

Žiadne komentáre

Vyhľadávanie

Kyocera - prve-zariadenia-formatu-a4-s-vykonom-a3

Najnovšie videá