Gyakorlati alapok II.

Nyugodtan hozzám vághatod a kivételedet! (throw)

 

A try-catch-finally blokkok és a Kivételfajták című fejezetekben ismertetett kivételkezelések egyik fontos közös tulajdonsága volt eddig, hogy a kivételkezelés "helyben", azaz vagy a main() függvényben, vagy abban a metódusban került lekezelésre, ahol maga a kivétel keletkezett. Ezt mindezidáig nem bonyolítottuk túl, azaz (minimálisan) a try-catch blokkpár, mondhatjuk úgy is, hogy lokálisan került implementálásra. Magasabb szempontú kódrendszerezéskor azonban ennek lehetnek hátrányai, hiszen a kivételt lokálisan kezeltük le, így megtörténhet, hogy ez a lokális kivételkezelés megtöri a magasabb szempontú rendezettséget.

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

Minél nagyobb, minél bonyolultabb és minél több rétegű egy projekt, annál inkább kényszerít minket, hogy a kódokat magasabb rendű szempontok alapján rendszerezzük, hiszen -emlékezzünk vissza-, talán a legfontosabb követelmény, hogy a kódban egy funkció csak egyszer fordulhat elő. Ez persze egy tapasztalt programozó számára magától értetődő folyamat (és voltaképpen minőségi elvárás), hiszen folyamatosan ilyen jellegű kódszerkezetekkel dolgozunk, csak gondoljunk például az API-ra, a Java előre megírt, logikus szempontok alapján rendszerezett, "belső" osztálykönyvtárára.

 

A problémát összefoglalva: a kivételt sokszor nem kezelhetjük le lokálisan, azaz keletkezési helyén. Mi hát akkor a megoldás?

 

Az ötlet már a C++ programozási nyelvben, mint a Java egyik közvetlen elődjében is megvalósításra került. A nyelvalkotók nagyon viccesen a következőt találták ki: a kivételt tovább kell dobni egy szinttel feljebb (throw = dob). Ekkor a felmerült problémát nem helyben kezeljük le, hanem átdobjuk egy másik metódusnak, lényegében valaki másnak, hogy legalább ne nekünk kelljen bajlódni vele.

 

A probléma szemléltetéséhez először ismételjük meg a "klasszikus", 0-val való osztás már orvosilag lekezelt kódját:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {
    public static void main(String[] args) {
    int a = 4;
    int b = 0;
    double c = 0;
    try{
        c = a / b;
        System.out.println(c);
    }
    catch(ArithmeticException ae){
        System.out.println("Hiba: nullával való osztás!");
        }
    }
}

 

Végeredmény:

Hiba: nullával való osztás!

 

Ezután ezt a defektes, de a hiba szempontjából lekezelt műveletet mindenestől tegyük bele egy külön metódusba:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {
static void nullavalValoOsztas(){
    int a = 4;
    int b = 0;
    double c = 0;
    try{
        c = a / b;
        System.out.println(c);
    }
    catch(ArithmeticException ae){
        System.out.println("Hiba: nullával való osztás!");
    }
}


public static void main(String[] args) {
    nullavalValoOsztas();
    }
}

 

Végeredmény:

Hiba: nullával való osztás!

 

Láthatóan a végeredmény ugyanaz. Ha a kódból kiemeljük a hibakezelést, természetesen ki fog akadni, illetve amint már megállapítottuk, a hibát valójában a JVM fogja elkapni:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {
static void nullavalValoOsztas(){
    int a = 4;
    int b = 0;
    double c = a / b;
}


public static void main(String[] args) {
    nullavalValoOsztas();
    }
}

 

Végeredmény:

Exception in thread "main" java.lang.ArithmeticException: / by zero
at Main.nullavalValoOsztas(Main.java:5)
at Main.main(Main.java:9)

 

Most pedig alkossunk egy olyan Java-kódot, amelyben a metódusok láncszerűen hívják meg egymást:

 

nullavalValoOsztas()

 

 

lustaDisznoMetodus1()

 

 

lustaDisznoMetodus2()

 

 

main()

 

Mivel a nullavalValoOsztas() metódus lesz a legelső, de hibás kód, a hibakezelésnek garantáltan végig kell futnia a metódusláncon.

 

Nézzük meg a futtatható Java-kódot:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {

static void nullavalValoOsztas(int a, int b){
    double c = a / b;
    System.out.println(c);
}

static void lustaDisznoMetodus1(){
    nullavalValoOsztas(4, 0);
}

static void lustaDisznoMetodus2(){
    lustaDisznoMetodus1();
}

public static void main(String[] args) {
try{
    lustaDisznoMetodus2();
}
    catch(ArithmeticException ae){
        System.out.println("Hiba: nullával való osztás!");
        }
    }
}

 

Végeredmény:

Hiba: nullával való osztás!

 

No de ácsi Karcsi bácsi! Hol van itt kivételdobás?

Sehol, mert a szabály a következő:

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

Kivétel továbbdobása (throw) csakis ellenőrzött kivételeknél (checked exceptions) lehetséges, futásidejű (runtime, unchecked exceptions) nem.

 

Ezen okból ismételjük meg a legfontosabb ellenőrzött és nem ellenőrzött kivételeket!

 

Gyakori ellenőrzött kivételek:

Gyakori nem ellenőrzött (futásidejű) kivételek:

A fenti példa -mivel az ArithmeticException nem ellenőrzött (futásidejű) kivétel-, nem igényel throw deklarációt.

 

A throw szemléltetésére ismételjük meg láncolt metódushívásainkat, ám immár egy ellenőrzött kivétellel. A kivételt először lokálisan, magában a kiváltó metódusban kezeljük le. A 2 db lustaDisznoMetodus() nem csinál semmit, csak meghívja a lánc előző tagját:

 

fajlOlvasas()

 

 

lustaDisznoMetodus1()

 

 

lustaDisznoMetodus2()

 

 

main()

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

import java.io.*;

public class Main {
static void
fajlOlvasas(){
    FileReader reader = null;
    try {
        reader = new FileReader("D:\\Kertész leszek.txt");
        BufferedReader br = new BufferedReader(reader);
        String line = br.readLine();

        while(line != null) {
            System.out.println(line);
            line = br.readLine();
        }

    } catch (FileNotFoundException fnfe) {
            System.out.println("A fájl nem található!");
    } catch (IOException ioe) {}
     finally {
        try {
            if (reader != null) {
                reader.close();
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }
}

static void lustaDisznoMetodus1(){
   
fajlOlvasas();
}

static void lustaDisznoMetodus2(){
    lustaDisznoMetodus1();
}

public static void main(String[] args) {
    lustaDisznoMetodus2();
    }
}

 

Végeredmény:

Kertész leszek, fát nevelek,
kelő nappal én is kelek,
nem törődök semmi mással,
csak a beojtott virággal.

Minden beojtott virágom
kedvesem lesz virágáron,
ha csalán lesz, azt se bánom,
igaz lesz majd a virágom.

Tejet iszok és pipázok,
jóhíremre jól vigyázok,
nem ér engem veszedelem,
magamat is elültetem.

Kell ez nagyon, igen nagyon,
napkeleten, napnyugaton -
ha már elpusztul a világ,
legyen a sírjára virág.

 

Ha a fájl valamilyen ok miatt nem található, akkor a kód végeredménye a következő  lesz:

 

Végeredmény:

A fájl nem található!

 

Ezután megpróbáljuk a hibát továbbdobni a throw utasítással, azaz nem lokálisan lekezelni.

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

Ennek során a hibakiváltó metódus fejléce mellett definiálnunk kell a throws utasítást, valamint a kivétel fajtáját is, például:

 

static void fajlOlvasas() throws IOException{

...

}

 

Illetve ugyanilyen módon kell definiálnunk minden metódus fejléce mellett, amelyik részt vesz az esetleges továbbítási láncban, azaz továbbdobja a kivételt, például:

 

static void lustaDisznoMetodus2()throws IOException{
    lustaDisznoMetodus1();
}

 

Nézzük meg a futtatható Java-kódot:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

import java.io.*;

public class Main {
static void fajlOlvasas() throws IOException{
    FileReader reader = null;
    reader = new FileReader("D:\\Kertész leszek.txt");
    BufferedReader br = new BufferedReader(reader);
    String line = br.readLine();
    while(line != null) {
        System.out.println(line);
        line = br.readLine();
    }
}

static void lustaDisznoMetodus1() throws IOException{
    fajlOlvasas();
}

static void lustaDisznoMetodus2() throws IOException{
    lustaDisznoMetodus1();
}

public static void main(String[] args) {
try{
    lustaDisznoMetodus2();
}
    catch (IOException ioe) {
        System.out.println("A fájl nem található!");
        }
    }
}

 

Végeredmény:

Kertész leszek, fát nevelek,
kelő nappal én is kelek,
nem törődök semmi mással,
csak a beojtott virággal.

Minden beojtott virágom
kedvesem lesz virágáron,
ha csalán lesz, azt se bánom,
igaz lesz majd a virágom.

Tejet iszok és pipázok,
jóhíremre jól vigyázok,
nem ér engem veszedelem,
magamat is elültetem.

Kell ez nagyon, igen nagyon,
napkeleten, napnyugaton -
ha már elpusztul a világ,
legyen a sírjára virág.

 

Ha a fájl valamilyen ok miatt nem található, akkor a kód végeredménye a következő  lesz:

 

Végeredmény:

A fájl nem található!

 

A throws definícióknál számos hibát véthetünk, ezt legtöbbször a JVM rendkívül magas szintű ellenőrzési programmechanizmusai fordítási hiba formájában jelzik (azaz a kód nem futtatható):

Például érdekes fordítási hibaértesítés a következő:

a BufferedReader osztály használata mindenképpen ellenőrzött kivételkezelést követel meg (BufferedReader br = new BufferedReader(reader);). Ehhez azonban mégsem elég egy FileNotFoundException kivétel-definiálás, hiszen egy fájl beolvasásakor számos egyéb hiba is felmerülhet, nemcsak FileNotFoundException. A megoldás, hogy a JVM már csak IOException kivételkezelést fogad el, sőt erről hibaüzenet formájában értesít is minket: Unhandled exception type IOException. Ekkor tulajdonképpen 1 szinttel feljebb léptünk a kivétel-hierarchiában (Exception hierarchy), hiszen FileNotFoundException az IOException leszármazottja:

 

java.lang.Throwable

java.lang.Exception

java.io.IOException

java.io.FileNotFoundException

 

A kivétel tehát valamilyen módon, lokálisan vagy lusta módon 1 vagy több szinttel továbbdobva, de lekezelésre került. A throw használatával kivételkezelés problémája átadható egy másik metódusnak, tágabb értelemben egy egészen máshol lévő, talán funkcionálisan is különböző osztálynak. Ebben a pillanatban már bármi lehetségessé válik: például lehetőségünk nyílik külön kivételkezelő osztály megalkotására, ahová folyamatosan érkeznek a kivételek, mint fontos hibaértesítő üzenetek.

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

Mert ha jobban belegondolunk, akkor a kivétel továbbdobása voltaképpen irányított hibaüzenet-továbbítás a kijelölt objektumok felé.