IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

1. diel - Výnimky v Jave

V tejto sekcii sa budeme venovať práci so súbormi. Než však môžeme začať zapisovať a čítať, mali by sme vyriešiť, ako ošetriť chybové stavy programu, ktorých pri práci so súbormi bude nastávať mnoho.

V našom programe môže často dôjsť k chybe. Tým nemyslím chybe z dôvodu, že bol program funkčne zle napísaný, takýchto chýb sa sme schopní dobre vyvarovať. Všeobecne ide najmä o chyby, ktoré zapríčinili tzv. vstupno/výstupné operácie. V anglickej literatúre sa hovorí o input/output alebo skrátene o IO. Ide napr. o vstup užívateľa z konzoly, zo súboru, výstup do súboru, na tlačiareň a podobne. V zásade platí, že tu figuruje užívateľ, ktorý nám môže zadať nezmyselný vstup, neexistujúci alebo nevalidný súbor, odpojiť tlačiareň a podobne. My však nenecháme program spadnúť s chybou, naopak budeme zraniteľné miesta v programe ošetrovať a na danú skutočnosť používateľa upozorníme.

Aktívne ošetrenie chýb

Prvú možnosť ošetrenia chýb nazývame ako aktívna. V programe nájdeme všetky zraniteľné miesta a ošetríme ich podmienkami. Ako učebnicový príklad sa spravidla používa delenie nulou. Predstavme si program, ktorý používa triedu Mathematics, ktorá má metódu divide(). Trieda by mohla vyzerať napr. takto:

class Mathematics {

    public static int divide(int a, int b) {
        return a / b;
    }

    // ...
}

Teraz triedu použijeme takýmto spôsobom:

System.out.println("Enter the dividend and the divisor to calculate the result:");
int a = Integer.parseInt(scanner.nextLine());
int b = Integer.parseInt(scanner.nextLine());
System.out.println(Mathematics.divide(a, b));

Pokiaľ teraz programu užívateľ zadá čísla 12 a 0, program spadne s chybou, pretože nulou nie je možné deliť. Aktívne chybu ošetríme jednoduchou podmienkou v programe:

System.out.println("Enter the dividend and the divisor to calculate the result:");
int a = Integer.parseInt(scanner.nextLine());
int b = Integer.parseInt(scanner.nextLine());
if (b != 0) {
    System.out.println(Mathematics.divide(a, b));
} else {
    System.out.println("You cannot divide by zero.");
}

Teraz si musíme pri každom použití metódy teda strážiť, či do druhého parametra nevkladáme nulu. Predstavte si, že by metóda brala parametrov 10 a používali sme ju v programe niekoľkokrát. Určite je veľmi zložité takto ošetrovať všetky vstupy vo všetkých prípadoch.

Riešením by mohlo byť vložiť kontrolu priamo do metódy. Máme tu však nový problém: Akú hodnotu vrátime, keď bude 2. parameter nulový? Potrebujeme hodnotu, z ktorej spoznáme, že výpočet neprebehol korektne. To je však problém, keď zvolíme napr. nulu, nepoznáme, či 0/12 je chybný výpočet alebo nie. Nevieme, či 0 značí výsledok alebo chybu. Ani zápornými číslami si nepomôžeme. Mohli by sme použiť nullovateľný typ Integer, ale ide to aj jednoduchšie a správnejšie. Parsovanie hodnôt je 2. klasický príklad zraniteľného vstupu od užívateľa. Ďalšie sú súborové operácie, kde súbor nemusí existovať, nemusíme naň mať práva, môže s ním byť práve pracované a podobne.

Pasívne ošetrenie chýb

Keď je operácia zložitejšia a bolo by príliš náročné ošetrovať všetky možné chybové stavy, nastupujú výnimky, tzv. pasívne ošetrenie chýb. Nás totiž vôbec nemusí zaujímať vnútorná logika v metóde, ktorú voláme. Pokúsime sa nebezpečnú časť kódu spustiť v "chránenom režime". Tento režim je nepatrne pomalší a líši sa tým, že pokiaľ dôjde k chybe, máme možnosť ju odchytiť a zabrániť pádu programu. O chybe tu hovoríme ako o výnimke. Využívame na to tzv. try-catch bloky:

try {
  // code that can throw an exception
} catch (Exception e) {
  // code that is executed when an exception is caught
}

Do bloku try umiestnime nebezpečnú časť kódu. Pokiaľ nastane v bloku try chyba, jeho vykonávanie sa preruší a program prejde do bloku catch. Pokiaľ všetko prebehne v poriadku, try sa vykoná celý a catch sa preskočí. Vyskúšajme si situáciu na našom predchádzajúcom príklade:

try {
    System.out.println(Mathematics.divide(a, b));
} catch (Exception e) {
    System.out.println("Error occurred.");
}

Kód je jednoduchší v tom, že nemusíme ošetrovať všetky zraniteľné miesta a premýšľať, čo všetko by sa mohlo pokaziť. Nebezpečný kód iba obalíme blokom try a všetky chyby sa zachytia v catch. Samozrejme do try-catch bloku umiestnime len to nevyhnutné, nie celý program :)

Teraz teda už vieme, ako ošetriť situácie, keď používateľ zadáva nejaký vstup, ktorý by mohol vyvolať chybu. Nemusí ísť len o súborové operácie, výnimky majú veľmi širokú oblasť použitia. Dokážeme náš program napísať tak, aby sa nedal jednoducho používateľom zhodiť.

V minulosti sme už bloky try a catch použili niekoľkokrát, bolo to najmä pri parsovaní dátumu a času.

Použitie výnimiek pri práci so súbormi

Ako už bolo povedané, súborové operácie môžu vyvolať mnoho výnimiek, preto sa súbory vždy pracujeme v try-catch bloku. Existuje aj niekoľko ďalších konštrukcií, ktoré pri výnimkách môžeme využívať.

Finally

Do try-catch bloku môžeme pridať ešte 3. blok a to finally. Ten sa spustí vždy, nech k výnimke došlo alebo nie. Predstavte si nasledujúcu metódu na uloženie nastavení. Metódy pre obsluhu súboru budú vymyslené:

public void saveSettings() {
    try {
        openFile("file.dat");
        writeToFile(settings);
    } catch (Exception e) {
        System.out.println("An error has occurred.");
    }
    if (fileIsOpened()) {
        closeFile();
    }
}

Metóda sa súbor pokúsi otvoriť a zapísať do neho objekt nastavenia. Pri chybe vypíše hlášku do konzoly. Otvorený súbor musíme opäť uzavrieť. Vypisovať chyby priamo v metóde je však škaredé, to napokon už vieme, metódy a objekty všeobecne by mali vykonávať len logiku a komunikáciu s užívateľom obstaráva ten, kto ich volá. Dajme teda metóde návratovú hodnotu boolean a vracajme true/false podľa toho, či sa operácia podarila alebo nie:

public boolean saveSettings() {
    try {
        openFile();
        writeToFile();
        return true;
    } catch (Exception e) {
        return false;
    }
    if (fileIsOpened()) {
        closeFile();
    }
}

Na prvý pohľad to vyzerá, že sa súbor vždy uzavrie. Celý kód je však v nejakej metóde, v ktorej voláme return. Ako vieme, return ukončí metódu a nič za ním sa už nevykoná.

Súbor by tu vždy zostal otvorený a uzavretie by sa už nevykonalo. Ako následok by to mohlo mať, že by bol potom súbor neprístupný. Pokiaľ vložíme zatvorenie súboru do bloku finally, vykoná sa vždy. Java si pamätá, že blok try-catch obsahoval finally a po opustení sekcie catch zavolá finally:

public boolean saveSettings() {
    try {
        openFile();
        writeToFile();
        return true;
    } catch (Exception e) {
        return false;
    } finally {
        if (fileIsOpened()) {
            closeFile();
        }
    }
}

Finally sa teda používa pri výnimkách na upratovacie práce, dochádza tu k zatváraniu súborov, uvoľňovaniu pamäte a podobne.

Pre každý typ súborov poskytuje Java triedu zapisovača a čítača (writer a reader). Metóda pre uloženie napr. nastavenia by v Jave reálne vyzerala asi takto:

public boolean saveSettings() {
    FileWriter w = null;
    try {
        w = new FileWriter("file.dat");
        w.write(object);
        return true;
    } catch (Exception e) {
        return false;
    } finally {
        if (w != null) {
            w.close();
        }
    }
}

Takto sa naozaj reálne so súbormi pracuje, iba triedu som si vymyslel. Do inštancie zapisovača umiestnime najprv null. Potom, už v bloku try, skúsime vytvoriť zapisovač na súbore file.dat a zapísať nejaký objekt. Pokiaľ sa všetko podarí, vrátime true (samozrejme sa potom ešte zavolá blok finally). Operácia môže zlyhať z dvoch dôvodov. Buď sa do súboru nepodarí zapísať alebo sa nám súbor pre zápis ani nepodarí otvoriť. Výnimku v každom prípade zachytíme a vrátime false, z čoho sa potom pozná, že sa metóde uloženia nepodarilo. Blok finally zatvorí súbor, ktorý zapisovač otvoril. Keďže sa ale otvorenie nemuselo podariť, musíme sa najskôr pozrieť, či sa zapisovač vôbec vytvoril, aby sme mali čo zatvárať. Metódu by sme volali napr. takto:

if (!saveSettings()) {
    System.out.println("Unable to save settings.");
}

Blok catch môžeme vynechať a nechať metódu, aby výnimku pokojne vyvolala. Budeme počítať s tým, že sa s výnimkou vysporiada ten, kto metódu zavolal, nie metóda sama. Je to tak lepšie, ušetríme návratovú hodnotu metódy (ktorú je potom možné použiť pre niečo iné) a kód sa nám zjednoduší:

public void saveSettings() throws Exception {
    FileWriter w = null;
    try {
        w = new FileWriter("file.dat");
        w.write(object);
    } finally {
        if (w != null) {
            w.close();
        }
    }
}

Všimnite si, že sme k hlavičke metódy pridali throws Exception. Tým Jave hovoríme, že vyvoláva výnimku a vďaka tomu nám prestane hubovať za chýbajúci catch blok. Metódu by sme teraz volali takto:

try {
    saveSettings();
} catch (Exception e) {
    System.out.println("Unable to save settings.");
}

Teraz si ukážeme, ako celú situáciu ešte viac zjednodušiť. Použijeme konštrukciu try-with-resources.

Try-with-resources

Java (od verzie 7) umožňuje značne zjednodušiť prácu s inštanciami tried na čítanie a zápis do súborov. Vyššie uvedený blok môžeme zapísať pomocou notácie try-with-resources, ktorá nahrádza bloky try a finally. Obrovskou výhodou je, že blok finally Java vygeneruje sama a sama zaistí, aby daná inštancia readeru alebo writera súbor uzavrela. Metóda saveSettings() by teda vyzerala s pomocou try-with-resources takto:

public void saveSettings() throws Exception
    try (FileWriter w = new FileWriter("file.dat")) {
        w.write(object);
    }
}

Vidíme, že sa kód extrémne zjednodušil, aj keď robí v podstate to isté. Pri volaní metódy opäť použijeme try-catch blok. Nezabudnite, že try-with-resources nahrádza iba try-finally, nie catch!. Metódu, v ktorej sa používa try-with-resources, musíme rovnako volať v try-catch bloku.

Teraz sme dospeli presne tam, kam som chcel. Na všetky manipulácie so súbormi totiž budeme v nasledujúcich tutoriáloch používať konštrukciu try-with-resources. Kód bude jednoduchší a nikdy sa nám nestane, že by sme súbor zabudli zavrieť.

K výnimkám sa ešte raz vrátime, ukážeme si, ako odchytávať len niektoré typy výnimiek, ktoré hotové triedy výnimiek môžeme v našich programoch používať a tiež, ako vytvoriť výnimku vlastnú. Teraz som však chcel vysvetliť len potrebné minimum pre prácu so súbormi a nie vám zbytočne pliesť hlavu zložitými konštrukciami :)

V budúcom dieli nášho seriálu tutoriálov o Jave, Úvod do práce so súbormi v Jave, si ukážeme, ako fungujú prístupové práva v operačných systémoch a ako sa vytvárajú inštancie tried Path a File.


 

Všetky články v sekcii
Práca so súbormi v Jave
Preskočiť článok
(neodporúčame)
Úvod do práce so súbormi v Jave
Článok pre vás napísal David Hartinger
Avatar
Užívateľské hodnotenie:
1 hlasov
David je zakladatelem ITnetwork a programování se profesionálně věnuje 15 let. Má rád Nirvanu, nemovitosti a svobodu podnikání.
Unicorn university David sa informačné technológie naučil na Unicorn University - prestížnej súkromnej vysokej škole IT a ekonómie.
Aktivity