Saturday, April 1, 2006

Java tutorial - Foutenafhandeling

Intro|Basics|Elementen|Bouwstenen|Datatypen|Operatoren|Toegangscontrole|Draaiboek|Foutenafhandeling|Objecten|Uitvoeren

Voor een stabiele Java code

Foutenafhandeling (exception handling)

Met foutenafhandeling (hierna wordt een fout een exceptie genoemd) kun je potentiële fouten afvangen, onderdrukken danwel doorgooien. Een exceptie is in Java een signaal dat er een onverwachte situatie is opgetreden, waardoor de code niet meer doorlopen kan worden. Bijvoorbeeld een object dat helemaal leeg (null) blijkt te zijn (NullPointerException), een rekensom waarbij door nul wordt gedeeld (ArithmeticException), een gegeven array index dat niet bestaat (ArrayIndexOutOfBoundsException), een bestand dat niet gevonden kan worden (FileNotFoundException), etcetera.

Excepties zijn in Java óók objecten. Ze behoren tot de java.lang.Throwable klasse. De Throwable klasse heeft twee subklassen: Error en Exception. De Error klasse handelt alleen interne fouten van de Java Virtual Machine af en zouden niet binnen de Java applicatie mogen worden afgevangen, dit soort fouten zijn toch onherstelbaar. De Exception klasse heeft op zich wederom een groot scala aan subklassen die op zich weer een aantal subklassen kunnen hebben.

Met uitzondering van Error, RuntimeException en alle subklassen daarvan, zijn alle excepties gecontroleerde fouten (checked exceptions). Dit betekent dat er door de compiler wordt vereist dat je alle potentiële excepties van deze categorie netjes moet afvangen. De Error, RuntimeException en al diens subklassen zijn ongecontroleerde fouten (unchecked exceptions) en je bent niet verplicht om deze in de code te afvangen, zoals de NullPointerException.

Terug naar boven

Try-catch-finally statement (try-catch-finally statement)

Met de try-catch-finally statement kun je de mogelijke onverwachte fouten afvangen en omzetten in duidelijke foutmeldingen richting de gebruiker. Ook kan dit worden gebruikt om onverwachte fouten te onderdrukken en vervolgens iets totaal anders te gaan doen.

De try-catch-finally statement:

try {
    statementblok
} catch (exceptie1 parameter1) {
    statementblok1
} catch (exceptie2 parameter2) {
    statementblok2
}
  ...
  finally {
    statementblok
}

De try statement heeft op z'n minst één catch of finally statement nodig. Je kunt meerdere catch blokken definieren, afhankelijk van de exceptie type. Wanneer je ten minste één catch statement hebt, dan is de finally statement optioneel.

Indien de statementblok van de try statement een exceptie afwerpt, dan wordt er binnen de catch blokken in de leesvolgorde gezocht naar de passende exceptie type. Indien deze wordt gevonden, dan wordt de bijbehorende statementblok van de catch uitgevoerd. Alle overige catch blokken worden dan genegeerd. Mocht er een finally statement aanwezig zijn, dan wordt deze altijd uitgevoerd, ongeacht het resultaat van de try statement. De parameter van de exceptie is in wezen de referentie-variabele van de opgevangen exceptie. Daar kun je in de catch statement verder mee werken.

package test;

import nl.balusc.voertuigen.Auto;

public class Klasse {

    public void methode() {

        // Declareer een lege auto.
        Auto standaardAuto = null;

        // Probeer de auto te starten.
        try {
            standaardAuto.starten();
            System.out.println("De auto is succesvol gestart.");
        } catch (NullPointerException e) {
            System.out.println("Starten mislukt, de auto bestaat niet.");

            // De "e" is een referentie-variabele van het
            // NullPointerException object. Op het moment is dat niet
            // noodzakelijk, maar je zou anders de volledige foutmelding
            // kunnen laten zien met de "e.printStackTrace();" statement.
            // Of je zou deze kunnen doorgooien met de throw statement,
            // zie ook later deze hoofdstuk.
        } finally {
            System.out.println("Gereed met proberen de auto te starten.");
        }
    }

}

De uitkomst:

Starten mislukt, de auto bestaat niet.
Gereed met proberen de auto te starten.

Wanneer een object niet is geinstantieerd (lees: wanneer de waarde van de referentie-variabele null is) en je wilt deze desondanks tóch aanroepen, dan wordt er door de Java Virtual Machine een NullPointerException afgeworpen. Je ziet dat er direct wordt gestopt na het uitvoeren van standaardAuto.starten(); en dat de daaropvolgende System.out.println statement niet wordt uitgevoerd. De NullPointerException wordt afgevangen door de bijpassende catch statement. De optionele finally statement wordt uiteraard uitgevoerd.

We zouden anders een voor de gebruiker onduidelijke foutmelding krijgen. Dit gaan we even testen. Dit kun je in een IDE uitvoeren, zoals Lomboz, zie ook Introductie - Hello World. De code van de klasse Auto kun je in Basics - Voorbeeld code vinden.

package test;

import nl.balusc.voertuigen.Auto;

public class Klasse {

    public void methode() {
        Auto standaardAuto = null;
        standaardAuto.starten();
    }

}
package test;

public class Test {

    public static void main(String[] args) {
        Klasse klasse = new Klasse();
        klasse.methode();
    }

}

De uitkomst na het uitvoeren van Test in een IDE:

Exception in thread "main" java.lang.NullPointerException
    at test.Klasse.methode(Klasse.java:9)
    at test.Test.main(Test.java:7)

In een nette IDE kun je overigens op de regels van de uitvoerconsole klikken om direct naar de regel te gaan waar de fout werd veroorzaakt. Ook kun je er het verloop van de applicatie teruglezen, deze moet je van onder naar boven lezen.

NullPointerException

Terug naar boven

Throw statement (throw statement)

Met de throw statement kun je desgewenst de referentie-variabele van de exceptie of een nieuw exceptie object naar smaak afwerpen. Deze wordt in regel gelogd naar de systeem log. De throw statement kun je in principe overal in de code gebruiken, maar het meest nette is toch om deze binnen een catch statement te uitvoeren of desnoods in een selectie statement.

Werp de referentie-variabele van de exceptie af naar de systeem console:

package test;

import nl.balusc.voertuigen.Auto;

public class Klasse {

    public void methode() {
        Auto standaardAuto = null;

        try {
            standaardAuto.starten();
            System.out.println("De auto is succesvol gestart.");
        } catch (NullPointerException e) {
            System.out.println("Starten mislukt, de auto bestaat niet.");

            // Werp de "e" af naar de systeem console.
            throw e;
        }
    }

}

De uitkomst:

Starten mislukt, de auto bestaat niet.
Exception in thread "main" java.lang.NullPointerException
    at test.Klasse.methode(Klasse.java:11)
    at test.Test.main(Test.java:7)

Werp een nieuw exceptie object af naar de systeem console:

package test;

import nl.balusc.voertuigen.Auto;

public class Klasse {

    public void methode() {
        Auto standaardAuto = null;

        try {
            standaardAuto.starten();
            System.out.println("De auto is succesvol gestart.");
        } catch (NullPointerException e) {
            // Werp een nieuw exceptie object af naar de systeem console.
            throw new RuntimeException("Starten mislukt, de auto bestaat niet.");
        }
    }

}

De uitkomst:

Exception in thread "main" java.lang.RuntimeException: Starten mislukt, de auto bestaat niet.
    at test.Klasse.methode(Klasse.java:15)
    at test.Test.main(Test.java:7)

Dit kun je ook binnen een selectie statement doen, bijvoorbeeld binnen de if-else statement. Gebruik dit alleen als een try-catch-finally statement echt niet toepasbaar is en je écht weet waar je mee bezig bent.

package test;

import nl.balusc.voertuigen.Auto;

public class Klasse {

    public void methode() {
        Auto standaardAuto = null;

        if (standaardAuto != null) {
            standaardAuto.starten();
            System.out.println("De auto is succesvol gestart.");
        } else {
            // Werp een nieuw exceptie object af naar de systeem console.
            throw new RuntimeException("Starten mislukt, de auto bestaat niet.");
        }
    }

}

De uitkomst is identiek:

Exception in thread "main" java.lang.RuntimeException: Starten mislukt, de auto bestaat niet.
    at test.Klasse.methode(Klasse.java:15)
    at test.Test.main(Test.java:7)

Terug naar boven

Throws bepaling (throws clause)

Wanneer je een gecontroleerde fout wil afwerpen, dan ben je verplicht om de throws bepaling aan de declaratie van de methode toe te voegen. In deze throws bepaling geef je dan de naam van de exceptie klasse mee. Let wel, de klassen Error, RuntimeException en alle subklassen daarvan vallen niet onder gecontroleerde fouten.

package test;

import nl.balusc.voertuigen.Auto;

public class Klasse {

    // De methode() declaratie met de "throws Exception" bepaling.
    public void methode() throws Exception {
        Auto standaardAuto = null;

        try {
            standaardAuto.starten();
            System.out.println("De auto is succesvol gestart.");
        } catch (Exception e) {
            // Let op: geef een "algemene" foutmelding, want je vangt met de
            // Exception superklasse eigenlijk álle soorten excepties af.
            System.out.println("Starten mislukt.");
            throw e;
        }
    }

}

Wanneer een methode een throws clause heeft, dan ben je verplicht om het aanroepen van de methode in een try statement of in de throws bepaling te zetten. Anders krijg je in alle gevallen de volgende foutmelding van de compiler:

Unresolved compilation problem: Unhandled exception type Exception

Unhandled exception

Het in een try statement zetten gaat dus als volgt (zie ook de volgende screenshot voor een Eclipse tip met CTRL+1):

Surround with try/catch

package test;

public class Test {

    public static void main(String[] args) {
        Klasse klasse = new Klasse();

        try {
            klasse.methode();
        } catch (Exception e) {
            System.out.println("Er is een exceptie opgetreden.");
        }
    }

}

Met als resultaat:

Starten mislukt.
Er is een exceptie opgetreden.

Handled exception

Het blijft een onduidelijke foutmelding (lees: erg algemeen), maar je begrijpt het punt hopelijk wel ;) Je zou desgewenst e.printStackTrace(); kunnen toevoegen aan de catch statement. Daarmee kun je het verloop van de applicatie volgen en de type foutmelding achterhalen.

package test;

public class Test {

    public static void main(String[] args) {
        Klasse klasse = new Klasse();

        try {
            klasse.methode();
        } catch (Exception e) {
            System.out.println("Er is een exceptie opgetreden.");
            e.printStackTrace();
        }
    }

}

Starten mislukt.
Er is een exceptie opgetreden.
java.lang.NullPointerException
    at test.Klasse.methode(Klasse.java:12)
    at test.Test.main(Test.java:9)

Terug naar boven

Voorkomen is beter dan genezen

Vergeet vooral niet: Voorkomen is beter dan genezen. Het is uiteindelijk niet de bedoeling om doelloos hele lappen Java code binnen één grote try blok te zetten met daaronder slechts een catch op Exception. Wees zo specifiek mogelijk in de afhandeling van fouten. Probeer in ieder geval ook om de ongecontroleerde fouten (unchecked exceptions) zoveel mogelijk te voorkomen door goed te programmeren. In het geval van bijvoorbeeld de NullPointerException zou je deze bijvoorbeeld als volgt kunnen voorkomen:

package test;

import nl.balusc.voertuigen.Auto;

public class Klasse {

    public void methode() {
        Auto standaardAuto = null;

        // Controleer of de standaardAuto niet leeg is.
        if (standaardAuto != null) {
            standaardAuto.starten();
        }

        // Of beter, instantieer het gewoon! ;)
        if (standaardAuto == null) {
            standaardAuto = new Auto();
        }
        standaardAuto.starten();
    }

}
Terug naar boven


In het volgende hoofdstuk worden de belangrijkste Object Oriented Programming aspecten van Java beschreven: Objecten.

Copyright - Niets van deze pagina mag worden overgenomen zonder uitdrukkelijke toestemming.

(C) April 2006, BalusC

No comments: