18. Kivételkezelés

A kivétel a „kivételes esemény” kifejezés rövidítése.

Definíció: A kivétel egy olyan esemény, amely a program végrehajtásakor keletkezik, megszakítva az utasítások végrehajtásának normális folyamatát.

Ha egy metódusban hiba keletkezik, a metódus egy objektumot hoz létre, melyet átad a futtatási környezetnek. Az objektum – melyet kivétel objektumnak neveznek – tartalmazza az információt a hibáról, annak típusáról és a program állapotáról, amikor a hiba létrejött. Kivétel objektum létrehozását és futtatási rendszer által történő kezelését kivételdobásnak hívják.

Miután egy metódus eldob egy kivételt, a futtató környezet megpróbál a kezelésére találni valamit. A lehetséges dolgok, melyek a kivételt kezelik a meghívott metódusok rendezett listája abban a metódusban, ahol a hiba keletkezett. A metódusok listáját hívási veremnek nevezzük.

Kivételkezelés

A futtató rendszer átkutatja a hívási vermet olyan metódus után, mely tartalmaz kivétel kezelésére alkalmas kódblokkot. Ezt a blokkot kivételkezelőnek nevezzük. A keresés abban a metódusban kezdődik, ahol a hiba generálódott, majd a hívási verem metódusainak fordított sorrendjében folytatódik. Mikor egy megfelelő kezelőt talál, a futtató rendszer, átadja a kivételt a kezelőnek. Egy kivételkezelő megfelelő, ha az eldobott kivétel objektum típusa megegyezik azzal a típussal, melyet a kezelő kezelni tud. A kivételkezelő kiválasztását úgy is nevezik, hogy elkapni a kivételt. Ha a futtatókörnyezet a metódusok átkutatása után sem talál megfelelő kivételkezelőt, mint ahogy a következő ábra mutatja, a futtató rendszer (és ez által a program) leáll.

Hibák kezelése

Hibák kezelésére használt kivételeknek van néhány előnye a hagyományos hibakezelési technikákkal szemben. (Később visszatérünk a témára.)

18.1. Kivételek elkapása vagy továbbengedése

A Java futtató rendszer megköveteli, hogy a metódus elkapja, vagy felsorolja az összes ellenőrzött kivételt, melyet a metódus eldobhat (másként fogalmazva a kivételt továbbengedi). Először is tekintsünk át néhány kifejezést.

Elkapás (catch)

A metódus elkaphat egy kivételt, ha rendelkezik ilyen típusú kivételek kezelőjével.

Továbbengedés (throws záradék)

A metódus a deklarációja throws záradékában írja le, hogy milyen kivételeket dobhat.

Ellenőrzött kivételek

Kétfajta kivétel létezik: futási időben keletkezett kivétel és nem futási időben keletkezett kivétel. Futási idejű kivétel a Java futtatórendszerében keletkezik: aritmetikus kivételek (például nullával való osztás), referenciával kapcsolatos kivételek (mint például egy objektum tagjaihoz való hozzáférés null hivatkozással) és indexeléssel kapcsolatos kivételek (mint például egy tömb elemeihez való hozzáférés olyan indexszel, mely túl nagy vagy túl kicsi). Egy metódusnak nem kötelező előírnia futási idejű kivételeket, de ajánlott.

Nem futási időben keletkezett kivételek olyan kivételek, melyek a Java futási rendszeren kívül keletkeznek. Például: kivételek, melyek I/O során keletkeznek. A fordító biztosítja, hogy a nem futási időben keletkezett kivételeket elkapják, vagy továbbengedjék; ezért ezeket ellenőrzött kivételeknek is nevezzük.

Sok programozó inkább futási időben keletkezett kivételeket használ az ellenőrzött kivételekkel szemben, hogy ne keljen elkapniuk vagy továbbengedniük őket. Ez általában nem ajánlott.

Kivételek, melyeket a metódus eldobhat

  • Minden a metódus által közvetlenül eldobott kivétel
  • Minden közvetetten eldobott kivétel másik metódus hívásával, mely kivételt dob.

Kivételek elkapása és kezelése

Ez a fejezet bemutatja, hogyan kell használni a kivételkezelő három komponensét – a try, catch, és finally blokkokat – egy kivételkezelő megírásához. A fejezet utolsó része végigmegy egy példán, bemutatva, mi történik különböző esetekben.

A következő példa a ListOfNumbers osztályt definiálja és implementálja. A ListOfNumbers konstruktor egy Vector objektumot hoz létre, mely tíz Integer elemet tartalmaz a nullától kilencig terjedő index értékekhez. A ListOfNumbers osztály egy writeList metódust is definiál, mely a számok listáját kiírja egy szövegfájlba, melynek neve OutFile.txt. A példa program a java.io csomagban definiált kimeneti osztályokat használja, melyekről részletesebben később lesz szó.

import java.io.*;
import java.util.Vector;
public class ListOfNumbers {
    private Vector vector;
    private static final int SIZE = 10;
    public ListOfNumbers () {
        vector = new Vector(SIZE);
        for (int i = 0; i < SIZE; i++) {
            vector.addElement(new Integer(i));
        }
    }
    public void writeList() {
        PrintWriter out = new PrintWriter(
            new FileWriter("OutFile.txt"));
        for (int i = 0; i < SIZE; i++) {
            out.println("Value at: " + i + " = " +
                vector.elementAt(i));
        }
        out.close();
    }
}

Az első vastag betűs sor egy konstruktor hívása. A konstruktor egy kimeneti folyamot inicializál. Ha a fájlt nem lehet megnyitni, a konstruktor IOException-t dob. A második vastag betűs sor a Vector osztály elementAt metódusát hívja meg, mely egy ArrayIndexOutOfBoundsException-t dob, ha az paramétereinek értéke túl kicsi (kisebb, mint nulla), vagy túl nagy (nagyobb, mint a változók száma, melyeket a Vector tartalmaz). Ha megpróbáljuk lefordítani a ListOfNumbers osztályt, a fordító egy hibaüzenetet ír ki a FileWriter konstruktor által dobott kivételről, de nem fog hibaüzenetet megjeleníteni az elementAt által dobott hibáról. Ennek oka, hogy a konstruktor által dobott kivétel, IOException, ellenőrzött kivétel, míg a másik, ArrayIndexOutOfBundsException, egy futási időben keletkezett kivétel. A Java programozási nyelv csak az ellenőrzött kivételek kezelését követeli meg, tehát a felhasználó csak egy hibaüzenetet fog kapni.

Most, hogy megismerkedünk a ListOfNumbers osztállyal és az abban eldobott kivételekkel, készen állunk egy kivételkezelő megírására, mely ezeket a hibákat elkapja, és kezelni tudja.

A try blokk

Egy kivételkezelő elkészítésének első lépése, hogy elhatároljuk a kódot, ami hibát dobhat a try blokkban. A try blokk általában így néz ki:

try {
    code
}
catch and finally blocks ...

A példakódban található szegmens tartalmaz egy vagy több olyan sort, amely kivételt dobhat. (A catch és a finally blokkokról részletes magyarázatot a következő részben találhatunk.)

Ahhoz, hogy egy kivételkezelőt készítsünk a ListOfNumbers osztály writeList metódusához, a writeList metódus kivétel-dobó részeit el kell határolnunk a try blokkal. Ezt többféleképpen tehetjük meg. A programkód azon sorait, melyekről feltételezzük, hogy kivételt dobhatnak, try blokkba tesszük, és mindegyiknél gondoskodunk a kivételek kezelésről. Vagy pedig betehetjük az összes writeList kódot egy egyszerű try blokkba, és ehhez hozzákapcsolhatunk többféle kivételkezelőt. A következőkben láthatjuk, hogy az egész metódusra használjuk a try blokkot, mivel a szóban forgó kód nagyon rövid:

...
private Vector vector;
private static final int SIZE = 10;
...
PrintWriter out = null;
try {
    System.out.println("Entered try statement");
    out = new PrintWriter(new FileWriter("OutFile.txt"));
    for (int i = 0; i < SIZE; i++) {
        out.println("Value at: " + i + " = " +
            vector.elementAt(i));
    }
} /// catch and finally statements ...

Amennyiben a kivétel bekövetkezik a try blokkon belül, a kivétel lekezelésre kerül a kivételkezelő által. Ahhoz, hogy a kivételkezelést hozzá tudjuk kapcsolni a try blokkhoz, utólag használnunk kell catch blokkot is. A következő rész megmutatja, hogyan is kell ezt használnunk.

Megjegyzés: A kivételkezeléssel ismerkedők esetén gyakori hiba, hogy csak a kivételt eldobó függvényhívást teszik a try blokkba. Érdemes ezen a példán átgondolni, hogy ha nem sikerülne az állomány megnyitása, akkor semmi értelme nem lenne a for ciklus lefutásának. A fenti megoldásnál ez nem is fog bekövetkezni, hiszen a kivétel létrejöttekor a vezérlés a teljes try blokkból kilép.

A catch blokk

A try blokkhoz hozzáilleszthetjük a kivételkezelést, amennyiben egy vagy több catch blokkot használunk közvetlenül a try blokk után. Semmilyen programkód nem lehet a try blokk vége és az első catch blokk között!

try {
    ...
} catch (ExceptionType name) {
    ...
} catch (ExceptionType name) {
    ...
} ...

Minden catch blokk egy kivételkezelő, és azt a típusú kivételt kezeli, amilyet a paraméter tartalmaz. A paraméter típusa (ExceptionType) deklarálja a kivétel típusát, amit a kezelő lekezel.

A catch blokk tartalmazza azt a programkódot, amely végrehajtásra kerül, amikor a kivételkezelőt meghívjuk. A futtatórendszer meghívja azt a kivételkezelőt, amelyik esetén az ExceptionType megfelel a dobott kivétel típusának.

Itt látható két kivételkezelő a writeList metódushoz – az ellenőrzött kivételek két típusához, melyeket a try blokk dobhat:

try {
    ...
} catch (FileNotFoundException e) {
  System.err.println("FileNotFoundException: "+e.getMessage());
  throw new SampleException(e);
} catch (IOException e) {
  System.err.println("Caught IOException: "+e.getMessage());
}

Mindkét kezelő egy hibaüzenetet ír ki. A második kezelő semmi mást nem hajt végre. Bármilyen IOException (I/O kivétel) elkapása esetén (amit az első kezelő nem kapott el), megengedi a programnak, hogy folytassa a futtatást.

Az első kezelő a kiírandó szövegek összeillesztése során egy saját definiált kivételt dob. Ebben a példában a FileNotFoundException kivétel létrejöttekor egy saját definiált kivételt hoz létre, aminek SampleException a neve, és ezt dobja a program.

A kivételkezelők többet is tudnak, mint hogy hibaüzeneteket írnak ki, vagy pedig megállítják a program futását. Hibajavításra is képesek, amint a felhasználó hoz egy rossz döntést, vagy pedig a hibák túlnőnek a legmagasabb szintű kezelőn láncolt kivételeket használva.

A finally blokk

A kivételkezelő beállításának utolsó lépése, hogy rendet tegyünk magunk után, mielőtt átadjuk az irányítást a program különböző részeinek. Ezt a takarító programkódot a finally blokkba kell beírnunk. A finally blokk tetszőlegesen használható. Olyan mechanizmust nyújt, ami kiküszöböli azokat a figyelmetlenségeket, amik a try blokkban történtek. A finally blokkot például arra használhatjuk, hogy bezárjuk a fájlokat, amelyekre se a hiba nélküli futás, se a hiba dobása esetén nem szükségesek már.

A writeList metódus try blokkja (amivel eddig dolgoztunk) megnyitja a PrintWriter-t. A writeList metódusból való kilépés előtt programnak be kellene zárnia ezt a folyamatot. Ez felvet egy némiképp komplikált problémát, mivel writeList metódus try blokkja a következő három lehetőség közül csak egyféleképpen tud kilépni:

  • A new FileWriter hibát jelez és IOException-t dob.
  • A vector.elementAt(i) hibát jelez és ArrayIndexOutOfBoundsException-t dob.
  • Minden sikerül és a try blokk zökkenőmentesen kilép.

A futtatórendszer mindig végrehajtja azokat az utasításokat, amelyek a finally blokkban vannak, függetlenül attól, hogy történt-e kivétel dobása, vagy nem. Így ez a legmegfelelőbb hely, hogy kitakarítsunk magunk után.

A következő finally blokk a writeList metódust takarítja ki, és bezárja a PrintWriter-t.

finally {
    if (out != null) {
        System.out.println("Closing PrintWriter");
        out.close();
    } else {
        System.out.println("PrintWriter not open");
    }
}

A writeList példában, a finally blokkba való beavatkozás nélkül tudunk gondoskodni a kitakarításról. Például, a PrintWriter bezárását végző programkódot a try blokk végéhez tudjuk illeszteni, továbbá az ArrayIndexOutOfBoundsException kivétel-kezelőhöz:

try {
    ...
    out.close();
} catch (FileNotFoundException e) {
    out.close();
    System.err.println(
            "Caught: FileNotFoundException: "+e.getMessage());
    throw new RuntimeException(e);
} catch (IOException e) {
    System.err.println("Caught IOException: " +
                        e.getMessage());
}

Azonban ez mégis lemásolja a kódot, így amennyiben később módosítjuk a programkódot, nehéz lesz olvasni benne, és a hibák kiterjedhetnek. Például, ha a try blokkhoz egy olyan programkóddal bővítjük, ami egy új típusú kivételt dobhat, emlékeznünk kell arra, hogy bezárjuk a PrintWriter-t az új kivétel-kezelőben.

Az összeillesztés

Az előző részekben volt arról szó, hogyan építsünk fel a ListOfNumbers osztályban try, catch és finally blokkokat a writeList metódus számára. A következőkben a forráskódot nézzük meg, vizsgálva a végeredményt.

Mindent egybevéve, a writeList metódus a következőképp néz ki:

public void writeList() {
    PrintWriter out = null;
    try {
        System.out.println("Entering try statement");
        out =
            new PrintWriter(new FileWriter("OutFile.txt"));
        for (int i = 0; i < SIZE; i++)
            out.println("Value at: " + i + " = "
              + vector.elementAt(i));
    } catch (ArrayIndexOutOfBoundsException e) {
        System.err.println("Caught " +
                    "ArrayIndexOutOfBoundsException: " +
                      e.getMessage());
    } catch (IOException e) {
        System.err.println("Caught IOException: " +
                         e.getMessage());
    } finally {
        if (out != null) {
            System.out.println("Closing PrintWriter");
            out.close();
        } else {
            System.out.println("PrintWriter not open");
        }
    }
}

Ahogy azt előzőekben említettük, a metódusban a try szerkezetnek három lehetséges kimenete lehet:

  • A kódunk hibás és kivételt dob. Ez lehet egy IOException kivétel, amit a FileWriter okoz vagy egy ArrayIndexOutOfBoundsException kivétel egy rosszul megadott érték esetén a for ciklusban, esetleg egy RuntimeException kivétel bármilyen futási hiba esetén.
  • Minden rendben, a várt kimenetet kapjuk.

Nézzük meg, mi történik a writeList metódusban ezen esetekben:

Kivétel történik

Számos oka lehet annak, hogy a FileWriter hibás működést fog eredményezni.

Okozhatja a konstruktor (ha nem tudjuk létrehozni a fájlt, vagy nem tudunk beleírni).

Amikor a FileWriter az IOException kivételt dobja, a program azonnal leállítja a try blokk végrehajtását. Ezek után a rendszer a metódus elejétől indul, és meghívja a megfelelő kivételkezelőt. Habár a FileWriter konstruktornak nincs megfelelő kivételkezelője, a rendszer megnézi a writeList metódust.

A writeList metódusnak két kivételkezelője van: egyik az IOException, másik, pedig az ArrayIndexOutOfBoundsException.

A rendszer megnézi a writeList kivételkezelőit, de csak a try blokk után. Az első kivételkezelő nem foglalkozik a konkrét kivétel típussal, ezért a rendszer automatikusan a második kivételkezelőt használja (IOException). Ez be tudja azonosítani a típusát, ennek köszönhetően a rendszer már megtalálja a megfelelő kivételkezelőt, így a catch blokk végrehajtódik.

Miután a kivételkezelő lefutott, a rendszer a finally blokkra ugrik, melynek futása elindul a kivételeket figyelmen kívül hagyva. Miután a finally blokk lefutott a rendszer normális ütemben fut tovább.

Az IOException által dobott kimenet:

Entering try statement
Caught IOException: OutFile.txt
PrintWriter not open

A try blokk normális működése

Minden, ami a try blokkon belül van, sikeresen lefut és megfelelő kimenetet ad eredményként. Nem kapunk kivételt és a rendszer továbblép a finally blokkra. A sikeresség eredményeképpen a PrintWriter megnyílik, majd amikor a rendszer eléri a finally blokkot, bezárja. Miután a finally blokk lefutott, a rendszer normális ütemben fut tovább.

A kivételek nélküli kimenet:

Entering try statement
Closing PrintWriter

Metódusok által dobott kivételek

Az előzőekben láthattuk, hogyan írhatunk kivételkezelőt a ListOfNumbers osztályban a writeList metódusnak. Olykor ez megfelelő a működést illetően, de vannak esetek, amikor jobb a veremkezelőt használni. Például, ha a ListOfNumbers osztályt egy másik osztály csomagjaként hozunk létre, ebben az esetben jobban tesszük, ha más módon kezeljük a kivételt.

Bizonyos esetekben szükség lehet az eredeti writeList metódus módosítására, melynek hatására már a kívánt működést kaphatjuk. Az eredeti writeList metódus, ami nem fordul le:

public void writeList() {
    PrintWriter out =
               new PrintWriter(new FileWriter("OutFile.txt"));
    for (int i = 0; i < SIZE; i++) {
        out.println("Value at: " + i + " = " +
                   victor.elementAt(i));
    }
    out.close();
}

A writerList metódus deklarációjában a throws kulcsszó hozzáadásával két kivételt adunk meg. A throws kulcsszót egy vesszővel elválasztott lista követ a kivételosztályokkal.

A throws a metódus neve és a paraméter lista után áll, utána következik a kapcsos zárójelben a definíció:

public void writeList() throws IOException,
                               ArrayIndexOutOfBoundsException {

Az ArrayIndexOutOfBoundsException futásidejű kivétel, nem kötelező lekezelni, így elegendő az alábbi forma használata:

public void writeList() throws IOException {

18.2. Kivételek dobása

Bármelyik forráskód tud kivételt dobni, legyen az saját kódunk, egy csomagból származó, más által írt kód, bármi is annak a kimenete, a throws minden esetben jelen van. A Java platform számos osztályt nyújt a kivételek kezelésére, melyek egytől egyig leszármazottjai a Throwable osztálynak. Ezek az osztályok lehetővé teszik a programok számára, hogy megkülönböztessék az eltérő típusú kivételeket, melyek a program futása közben keletkeznek.

Saját magunk is létrehozhatunk speciális osztályokat a leendő problémák reprezentációjával. Fejlesztőként saját magunknak kell létrehoznunk az osztályt és elvégezni a szükséges beállításokat.

A throw használata

A kivétel dobásánál minden metódus a throw kulcsszót használja, melynek a következő formai követelménynek kell, hogy megfeleljen:

throw someThrowableObject;

Az alábbi pop metódus egy osztály által létrehozott közös veremobjektumból jön létre. A metódus eltávolítja a verem felső rekeszét és az objektummal tér vissza:

public Object pop() throws EmptyStackException {
    Object obj;
    if (size == 0) {
        throw new EmptyStackException();
    }
    obj = objectAt(SIZE - 1);
    setObjectAt(SIZE - 1, null);
    size--;
    return obj;
}

A pop metódus megnézi, van-e valami a veremben. Ha a verem üres (mérete 0), akkor a pop által létrejön egy új EmptyStackException objektumot (a java.util tagja) és eldobja azt. Ezek az objektumok a java.lang.Throwable osztály leszármazottjai kell, hogy legyenek, a fejezet későbbi része a kivétel osztályok létrehozásával foglalkozik.

A pop metódus deklarációja magában foglalja a throws kulcsszót. Az EmptyStackException kivételt a pop metódus nem kapja el, így a metódusnak a throws kulcsszót kell, hogy használja a deklarációhoz.

18.3. Eldobható osztályok és leszármazottai

Az ábra a Throwable osztály és annak legjelentősebb származtatott osztályait (osztályhierarchiáját) reprezentálja. Amint láthatjuk, a Throwable osztálynak kettő direkt módon származtatott utódja van: Error és Exception.

Throwable Class

Error osztály

Ha a művelet valamilyen okból kifolyólag nem hajtható végre, sikertelen a végrehajtás, a Java virtuális gép Error-t fog dobni. Egyszerű programok nem kapnak el, illetve nem dobnak Error-t.

Exception osztály

A legtöbb program elkap és dob objektumokat, melyek az Exception osztályból származnak. A legtöbb általunk megírt program elkap és dob kivételeket (jelzi a hibát).

Az Exception osztálynak számos leszármazottja definiált a Java Platformban, ezek a leszármazottak jelzik a különböző típusú kivételeket. Például IllegalAccessException jelzi, ha egy különös metódus nem található, a NegativeArraySizeException pedig, ha egy tömb méretét helytelenül adtunk meg (negatív szám).

A RuntimeException kivételek futás közben generálódnak a Java Virtuális gép által. Ennek egyik példája a NullPointerException, mely akkor jön létre, ha egy metódus megpróbál hozzáférni null hivatkozással valamely objektumhoz.

18.4 Láncolt kivételek

A kivételeknek sok esetben egy másik kivétel az oka. Ilyen esetben célszerű az eredeti (okozó) kivételt és az új kivételt együtt kezelni. Erre a problémára jelent megoldást a Java 1.4 verzió óta rendelkezésre álló kivételláncolás. Ennek lényege, hogy egy kivétel létrehozásakor becsomagoljuk az okozó kivételt az új kivétel objektumba.

Ennek érdekében két új metódus és két új konstruktor lett hozzáadva a Throwable típushoz.

A következő új metódusok és konstruktorok támogatják a láncolt kivételeket a Throwable osztályban:

Throwable getCause()
Throwable initCause(Throwable)
Throwable(String, Throwable)
Throwable(Throwable)

Az initCause és a Throwable konstruktorok Throwable paramétere egy kivétel, amik okozták az aktuális kivételt. A getCause visszaad egy kivételt, ami okozta az aktuális kivételt, az initCause pedig visszaadja az aktuális kivételt.

A következő példa megmutatja, hogyan használjuk a láncolt kivételeket:

try {
    ...
} catch (IOException e) {
    throw new SampleException("Other IOException", e);
}

Ebben a példában, amikor az IOException-t kap el a program, akkor egy új SampleException kivétel jön létre az eredeti okkal egybekötve, és a kivételek lánca feldobódik egy következő, magasabb szintű kivételkezelőhöz.

Hívási verem információk kinyerése

Most feltételezzünk, hogy egy magasabb szintű kivételkezelő akarja a kimenetre írni a hívási verem tartalmát egyedi formátumban.

Definíció: a hívási verem információt szolgáltat az aktuális szál futási történetéről, az osztályok és a metódusok neveiről, amik akkor hívódnak meg, amikor a kivétel bekövetkezett. A hívási verem egy nagyon hasznos hibakereső eszköz.

A következő kód megmutatja, hogyan kell a getStackTrace metódust használni a kivétel objektumon:

catch (Exception cause) {
    StackTraceElement elements[] = cause.getStackTrace();
    for (int i = 0, n = elements.length; i < n; i++) {      
        System.err.println(elements[i].getFileName() + ":"
               + elements[i].getLineNumber() + ">> "
               + elements[i].getMethodName() + "()");
    }
}

API naplózás

A következő kódrészlet catch blokkjában naplózásra látunk példát. Bármennyire célszerűnek tűnik is az előző példa megközelítése, érdemes inkább a naplózás mechanizmusát alkalmazni a java.util.logging segítségével:

try {
    Handler handler = new FileHandler("OutFile.log");
    Logger.getLogger("").addHandler(handler);
} catch (IOException e) {
    Logger logger = Logger.getLogger("package.name");
    StackTraceElement elements[] = e.getStackTrace();
    for (int i = 0; n = elements.length; i < n; i++) {
        logger.log(Level.WARNING, elements[i].getMethodName());
    }
}

18.5 Saját kivétel osztályok létrehozása

Amikor szükségessé válik egy kivétel dobása, az egyik lehetőség egy valaki által már megírtat használni – a Java Platform rengeteg kivétel osztállyal van ellátva – vagy magunk is tudunk írni a céljainknak megfelelőt.

Saját kivétel osztályt kell írnunk, ha a következő kérdések bármelyikére igennel válaszolhatunk. Különben valószínűleg egy valaki más által megírtat kell használnunk.

  • Olyan típusú kivétel osztályra van-e szükség, ami nem áll rendelkezésre a Java Platformban?
  • Fog segíteni a felhasználóknak, ha képesek lesznek megkülönböztetni a mi kivételeinket a mások által megírt kivételosztályoktól?
  • A kódunk több helyen fog-e kivételt dobni?
  • Ha valaki más kivételeit használjuk, a felhasználó hozzá fognak-e tudni férni azokhoz a kivételekhez?
  • Egy hasonló kérdés: a csomagunknak függetlennek és önállónak kell-e lennie?

Tegyük fel, hogy írtunk egy láncolt lista osztályt, amit szeretnénk megosztani nyílt forrású programként. A láncolt lista osztályunk többek között a következő metódusokat támogatja:

objectAt(int n)

Visszaadja a lista n-dik pozíciójában levő objektumot. Kivételt dob, ha a paraméter kisebb, mint 0, vagy nagyobb, mint az aktuális objektumok száma a listában.

firstObject()

Visszaadja az első objektumot a listában. Kivételt dob, ha a lista nem tartalmaz objektumot.

indexOf(Object o)

Átnézi a listát egy speciális objektumért és visszaadja a pozícióját a listában. Kivételt dob, ha az objektum nincs a listában.

A láncolt lista osztály képes különböző kivételeket dobni, ugyanakkor lehetőséget ad arra is, hogy a felhasználó program el tudja kapni a láncolt lista által dobott összes kivételt egy kivételkezelővel. Ha a láncolt listánkat egy csomagban tervezzük megosztani, minden kapcsolódó kódnak egy közös csomagban kell lennie.

A következő ábra illusztrál egy lehetséges osztályhierarchiát a láncolt listák által dobható kivételekről:

Linked List Exception

Szülőosztály választása

Az Exception valamennyi leszármazott osztályát lehet a LinkedListException szülőosztályának választani. Azonban könnyen látható, hogy ezek a leszármazott osztályok nem odaillők, mert vagy túl specializáltak, vagy nem függnek össze a LinkedListException-al. Ezért a LinkedListException szülő osztályának az Exception-nek kell lennie.

A legtöbb applet és alkalmazás, amit írunk, Exception objektumokat dob.

Figyelem: az olvasható kód érdekében egy jó gyakorlat, hogy hozzáfűzzük az Exception szót minden osztály nevéhez, amelyik közvetlenül vagy közvetve öröklődik az Exception osztályból.

18.6 Ellenőrző kérdések

  • Mi a kivétel?
  • Hogyan lehet kivételeket létrehozni?
  • Kell-e deklarálni a metódusban létrehozott kivételeket, és ha igen, hogyan?
  • Mire szolgál a try-catch?
  • Hány catch ága lehet egy try-catch szerkezetnek?
  • Hány finally ága lehet egy try-catch-finally szerkezetnek?
  • Mi a teendő, ha metódusunk olyan másik metódust hív meg, ami kivételt generálhat?
  • Mik a nem ellenőrzött kivételek, és hogyan lehet elkapni őket?

Korrekt a következő kód?

try {
   
} finally {
   
}

Milyen típusú kivételeket fog elkapni a következő kezelő?

catch (Exception e) {
    ...
}

Igaz vagy hamis? Indokolja!

  • A metódusban létrejövő kivételt kötelesek vagyunk a metódusban elkapni.
  • kivételt dobni a throws utasítással lehet.