Zoeken…


Invoering

Objecten van het type Throwable en zijn subtypen kunnen de stapel op worden gestuurd met het sleutelwoord throw en worden gepakt met try…catch verklaringen.

Syntaxis

  • void someMethod () gooit SomeException {} // methodeverklaring, dwingt methodeaanroepen om te vangen als SomeException een gecontroleerd uitzonderingstype is

  • proberen {

    someMethod(); //code that might throw an exception 
    

    }

  • catch (SomeException e) {

     System.out.println("SomeException was thrown!"); //code that will run if certain exception (SomeException) is thrown
    

    }

  • Tenslotte {

     //code that will always run, whether try block finishes or not
    

    }

Een uitzondering vangen met try-catch

Een uitzondering kan worden opgevangen en verwerkt met de instructie try...catch . (In feite try uitspraken andere vormen aan te nemen, zoals beschreven in andere voorbeelden over try...catch...finally en try-with-resources .)

Try-catch met één vangblok

De meest eenvoudige vorm ziet er zo uit:

try {
    doSomething();
} catch (SomeException e) {
    handle(e);
}
// next statement

Het gedrag van een simpele try...catch is als volgt:

  • De instructies in het try blok worden uitgevoerd.
  • Als er geen uitzondering wordt gegenereerd door de instructies in het try blok, gaat de besturing door naar de volgende instructie na de try...catch .
  • Als een uitzondering binnen het try blok wordt gegooid.
    • Het uitzonderingsobject wordt getest om te zien of het een instantie van SomeException of een subtype is.
    • Zo ja, dan is de catch blok zal vangen de uitzondering:
      • De variabele e is gebonden aan het uitzonderingsobject.
      • De code binnen het catch blok wordt uitgevoerd.
      • Als die code een uitzondering genereert, wordt de nieuw gegenereerde uitzondering gepropageerd in plaats van de oorspronkelijke.
      • Anders gaat de besturing door naar de volgende verklaring na de try...catch .
    • Als dit niet het geval is, blijft de oorspronkelijke uitzondering zich verspreiden.

Try-vangst met meerdere vangsten

Een try...catch kan ook meerdere catch . Bijvoorbeeld:

try {
    doSomething();
} catch (SomeException e) {
    handleOneWay(e)
} catch (SomeOtherException e) {
    handleAnotherWay(e);
}
// next statement

Als er meerdere catch , worden ze één voor één geprobeerd, beginnend met de eerste, totdat een uitzondering wordt gevonden voor de uitzondering. De bijbehorende handler wordt uitgevoerd (zoals hierboven) en vervolgens wordt de besturing doorgegeven aan de volgende instructie na de instructie try...catch . De catch blokken na degene die wedstrijden worden altijd overgeslagen, zelfs als de handler code genereert een uitzondering.

De "top down" matching-strategie heeft gevolgen voor gevallen waarin de uitzonderingen in de catch niet onsamenhangend zijn. Bijvoorbeeld:

try {
    throw new RuntimeException("test");
} catch (Exception e) {
    System.out.println("Exception");
} catch (RuntimeException e) {
    System.out.println("RuntimeException");
}

Dit codefragment zal "Uitzondering" uitvoeren in plaats van "RuntimeException". Aangezien RuntimeException een subtype van Exception , wordt de eerste (meer algemene) catch gematcht. De tweede (meer specifieke) catch zal nooit worden uitgevoerd.

De les die hieruit moet worden getrokken, is dat de meest specifieke catch (in termen van de uitzonderingstypes) als eerste moeten verschijnen, en de meest algemene als laatste. (Sommige Java-compilers waarschuwen u als een catch nooit kan worden uitgevoerd, maar dit is geen compilatiefout.)

Blokken met meerdere uitzonderingen

Java SE 7

Beginnend met Java SE 7 kan een enkel catch blok een lijst met niet-gerelateerde uitzonderingen verwerken. Het uitzonderingstype wordt weergegeven, gescheiden door een verticaal balksymbool ( | ). Bijvoorbeeld:

try {
    doSomething();
} catch (SomeException | SomeOtherException e) {
    handleSomeException(e);
} 

Het gedrag van een vangst met meerdere uitzonderingen is een eenvoudige uitbreiding voor het geval met één uitzondering. De catch overeen als de geworpen uitzondering overeenkomt met (minstens) een van de vermelde uitzonderingen.

De specificatie bevat nog wat extra subtiliteit. Het type e is een synthetische unie van de uitzonderingen in de lijst. Wanneer de waarde van e wordt gebruikt, is het statische type het minst voorkomende supertype van de typeassie. Echter, als e wordt rethrown binnen de catch blok, het type uitzondering die worden gegooid zijn de soorten in de unie. Bijvoorbeeld:

public void method() throws IOException, SQLException
    try {
        doSomething();
    } catch (IOException | SQLException e) {
        report(e);
        throw e;
    }

In het bovenstaande zijn IOException en SQLException gecontroleerde uitzonderingen waarvan het minst voorkomende supertype Exception . Dit betekent dat het report methode moet overeenkomen report(Exception) . Echter, de compiler weet dat de throw slechts een kan gooien IOException of een SQLException . De method kan dus worden verklaard als throws IOException, SQLException plaats van throws Exception . (Dat is een goede zaak: zie Pitfall - Throwing Throwable, Exception, Error of RuntimeException .)

Een uitzondering gooien

Het volgende voorbeeld toont de basisprincipes van het gooien van een uitzondering:

public void checkNumber(int number) throws IllegalArgumentException {
    if (number < 0) {
        throw new IllegalArgumentException("Number must be positive: " + number);
    }
}

De uitzondering wordt op de 3e regel gegenereerd. Deze verklaring kan worden onderverdeeld in twee delen:

  • new IllegalArgumentException(...) maakt een instantie van de klasse IllegalArgumentException , met een bericht dat de fout beschrijft die door de uitzondering wordt gerapporteerd.

  • throw ... werpt dan het uitzonderingsobject.

Wanneer de uitzondering wordt gegenereerd, worden de omringende instructies abnormaal beëindigd totdat de uitzondering wordt afgehandeld . Dit wordt beschreven in andere voorbeelden.

Het is een goede gewoonte om het uitzonderingsobject zowel in een enkele instructie te maken als te gooien, zoals hierboven weergegeven. Het is ook een goede gewoonte om in de uitzondering een betekenisvolle foutmelding op te nemen om de programmeur te helpen de oorzaak van het probleem te begrijpen. Dit is echter niet noodzakelijkerwijs het bericht dat u aan de eindgebruiker zou moeten tonen. (Om te beginnen biedt Java geen directe ondersteuning voor het internationaliseren van uitzonderingsberichten.)

Er zijn nog een paar punten te maken:

  • We hebben het checkNumber verklaard als throws IllegalArgumentException . Dit was niet strikt noodzakelijk, omdat IllegalArgumentException een gecontroleerde uitzondering is; zie de Java-uitzonderingshiërarchie - Niet-aangevinkte en aangevinkte uitzonderingen . Het is echter een goede gewoonte om dit te doen en ook de uitzonderingen op te nemen die de javadoc-opmerkingen van een methode bevatten.

  • Code direct na een throw is onbereikbaar . Vandaar dat als we dit schreven:

     throw new IllegalArgumentException("it is bad");
     return;
    

    de compiler zou een compilatiefout rapporteren voor de return .

Uitzondering chaining

Veel standaarduitzonderingen hebben een constructor met een tweede cause naast het conventionele message . Met de cause kunt u uitzonderingen koppelen. Hier is een voorbeeld.

Eerst definiëren we een ongecontroleerde uitzondering die onze toepassing gooit wanneer deze een niet-herstelbare fout tegenkomt. Merk op dat we een aannemer die een accepteert hebben opgenomen cause argument.

    public class AppErrorException extends RuntimeException {
        public AppErrorException() {
            super();
        }

        public AppErrorException(String message) {
            super(message);
        }

        public AppErrorException(String message, Throwable cause) {
            super(message, cause);
        }
    }

Hierna volgt een code die uitzonderingsketen illustreert.

    public String readFirstLine(String file) throws AppErrorException {
        try (Reader r = new BufferedReader(new FileReader(file))) {
            String line = r.readLine();
            if (line != null) {
                return line;
            } else {
                throw new AppErrorException("File is empty: " + file);
            }
        } catch (IOException ex) {
            throw new AppErrorException("Cannot read file: " + file, ex);
        }
    }

De throw binnen het try blok detecteert een probleem en meldt dit via een uitzondering met een eenvoudig bericht. De throw binnen het catch blok daarentegen IOException de IOException door deze in een nieuwe (aangevinkte) uitzondering te verpakken. Het gooit echter niet de oorspronkelijke uitzondering weg. Door de IOException als cause , registreren we deze zodat deze in de stacktrace kan worden afgedrukt, zoals uitgelegd in Stacktraces maken en lezen .

Aangepaste uitzonderingen

Onder de meeste omstandigheden is het vanuit het oogpunt van codeontwerp eenvoudiger om bestaande generieke Exception te gebruiken bij het maken van uitzonderingen. Dit geldt met name als u alleen de uitzondering nodig hebt om een eenvoudig foutbericht te verzenden. In dat geval heeft RuntimeException meestal de voorkeur, omdat het geen aangevinkte uitzondering is. Er zijn andere uitzonderingsklassen voor veelvoorkomende foutenklassen:

Gevallen waarin u wilt een aangepaste uitzondering klasse te gebruiken zijn de volgende:

  • U schrijft een API of bibliotheek voor gebruik door anderen, en u wilt gebruikers van uw API in staat stellen om uitzonderingen van uw API specifiek te vangen en af te handelen en deze uitzonderingen te kunnen onderscheiden van andere, meer generieke uitzonderingen .
  • U gooit uitzonderingen voor een specifiek soort fout in een deel van uw programma, dat u wilt vangen en afhandelen in een ander deel van uw programma, en u wilt deze fouten kunnen onderscheiden van andere, meer generieke fouten.

U kunt uw eigen aangepaste uitzonderingen maken door uitbreiding RuntimeException voor een ongecontroleerde exception of gecontroleerd uitzondering door een verruiming van enige Exception die niet ook subklasse van RuntimeException, omdat:

Subklassen van uitzondering die niet ook subklassen van RuntimeException zijn, zijn aangevinkte uitzonderingen

public class StringTooLongException extends RuntimeException {
    // Exceptions can have methods and fields like other classes
    // those can be useful to communicate information to pieces of code catching
    // such an exception
    public final String value;
    public final int maximumLength;

    public StringTooLongException(String value, int maximumLength){
        super(String.format("String exceeds maximum Length of %s: %s", maximumLength, value));
        this.value = value;
        this.maximumLength = maximumLength;
    }
}

Die kunnen worden gebruikt als vooraf gedefinieerde uitzonderingen:

void validateString(String value){
    if (value.length() > 30){
        throw new StringTooLongException(value, 30);
    }
}

En de velden kunnen worden gebruikt waar de uitzondering wordt gevangen en verwerkt:

void anotherMethod(String value){
    try {
        validateString(value);
    } catch(StringTooLongException e){
        System.out.println("The string '" + e.value + 
                "' was longer than the max of " + e.maximumLength );
    }
}

Houd er rekening mee dat volgens Oracle's Java Documentation :

[...] Als van een cliënt redelijkerwijs kan worden verwacht dat deze van een uitzondering herstelt, maak er dan een gecontroleerde uitzondering van. Als een client niets kan doen om van de uitzondering te herstellen, maak er dan een ongecontroleerde uitzondering van.

Meer:

De instructie try-with-resources

Java SE 7

Omdat de try-catch-slotverklaring voorbeeld illustreert, resource opruimen met behulp van een finally clausule vereist een aanzienlijke hoeveelheid "boiler-plate" code naar de rand-gevallen correct uit te voeren. Java 7 biedt een veel eenvoudigere manier om dit probleem aan te pakken in de vorm van de instructie try-with-resources .

Wat is een bron?

Java 7 heeft de java.lang.AutoCloseable interface geïntroduceerd waarmee klassen kunnen worden beheerd met behulp van de instructie try-with-resources . Instanties van klassen die AutoCloseable implementeren, worden bronnen genoemd . Deze moeten meestal op tijd worden afgevoerd in plaats van te vertrouwen op de vuilnisman om ze te verwijderen.

De AutoCloseable interface definieert een enkele methode:

public void close() throws Exception

Een methode close() moet de bron op de juiste manier verwijderen. De specificatie stelt dat het veilig moet zijn om de methode aan te roepen op een bron die al is verwijderd. Bovendien worden klassen die Autocloseable implementeren sterk aangemoedigd om de methode close() te declareren om een meer specifieke uitzondering te Autocloseable dan Exception , of helemaal geen uitzondering.

Een breed scala aan standaard Java-klassen en -interfaces implementeren AutoCloseable . Waaronder:

  • InputStream , OutputStream en hun subklassen
  • Reader , Writer en hun subklassen
  • Socket en ServerSocket en hun subklassen
  • Channel en zijn subklassen, en
  • de JDBC-interfaces Connection , Statement en ResultSet en hun subklassen.

Toepassing en klassen van derden kunnen dit ook doen.

De standaard try-with-resource-instructie

De syntaxis van een try-with-resources is gebaseerd op klassieke try-catch- , try-eindelijk- en try-catch-eindelijk- vormen. Hier is een voorbeeld van een "basisvorm"; dat wil zeggen de vorm zonder een catch of finally .

try (PrintStream stream = new PrintStream("hello.txt")) {
    stream.println("Hello world!");
}

De te beheren bronnen worden aangegeven als variabelen in de (...) sectie na de try clausule. In het bovenstaande voorbeeld declareren we een stream resource-variabelen en initialiseren deze naar een nieuw gemaakte PrintStream .

Nadat de bronvariabelen zijn geïnitialiseerd, wordt het try blok uitgevoerd. Wanneer dat is voltooid, wordt stream.close() automatisch aangeroepen om ervoor te zorgen dat de bron niet lekt. Merk op dat de aanroep close() gebeurt, ongeacht hoe het blok is voltooid.

De verbeterde try-with-resource-verklaringen

De instructie try-with-resources kan worden uitgebreid met catch en finally blokken, net als met de syntaxis van pre-Java 7 try-catch-eindelijk . De volgende codefragment voegt een catch blok naar onze vorige om te gaan met de FileNotFoundException dat de PrintStream aannemer kan gooien:

try (PrintStream stream = new PrintStream("hello.txt")) {
    stream.println("Hello world!");
} catch (FileNotFoundException ex) {
    System.err.println("Cannot open the file");
} finally {
    System.err.println("All done");
}

Als de resource-initialisatie of het try-blok de uitzondering genereert, wordt het catch blok uitgevoerd. Het finally blok wordt altijd uitgevoerd, zoals bij een conventionele try-catch-eindelijk- instructie.

Er zijn echter een paar dingen waar u op moet letten:

  • De resource-variabele valt buiten het bereik van de catch en blokkeert finally .
  • Het opruimen van bronnen vindt plaats voordat de instructie probeert het catch blok te matchen.
  • Als de automatische resource opschonen heeft een uitzondering, dan is dat zou kunnen worden gevangen in een van de catch blokken.

Meerdere bronnen beheren

De bovenstaande codefragmenten laten zien dat één resource wordt beheerd. Try-with-resources kan in feite meerdere bronnen in één statement beheren. Bijvoorbeeld:

try (InputStream is = new FileInputStream(file1);
     OutputStream os = new FileOutputStream(file2)) {
    // Copy 'is' to 'os'
}

Dit gedraagt zich zoals je zou verwachten. Zowel is als os worden automatisch gesloten aan het einde van het try blok. Er zijn een paar aandachtspunten:

  • De initialisaties vinden plaats in de codevolgorde, en latere initialiseerbare bronvariabele kan de waarden van de eerdere gebruiken.
  • Alle resourcevariabelen die met succes zijn geïnitialiseerd, worden opgeschoond.
  • Bronvariabelen worden opgeruimd in omgekeerde volgorde van hun aangiften.

In het bovenstaande voorbeeld is dus vóór os geïnitialiseerd en daarna opgeruimd en is deze opgeschoond als er een uitzondering is tijdens het initialiseren van os .

Gelijkwaardigheid van try-with-resource en klassieke try-catch-eindelijk

De Java Language Specification specificeert het gedrag van try-with-resource- formulieren in termen van de klassieke try-catch-eindelijk- instructie. (Raadpleeg de JLS voor de volledige details.)

Bijvoorbeeld, deze eenvoudige try-with-resource :

try (PrintStream stream = new PrintStream("hello.txt")) {
    stream.println("Hello world!");
}

is gedefinieerd als gelijk aan deze try-catch-eindelijk :

// Note that the constructor is not part of the try-catch statement
PrintStream stream = new PrintStream("hello.txt");

// This variable is used to keep track of the primary exception thrown
// in the try statement. If an exception is thrown in the try block,
// any exception thrown by AutoCloseable.close() will be suppressed.
Throwable primaryException = null;

// The actual try block
try {
    stream.println("Hello world!");
} catch (Throwable t) {
    // If an exception is thrown, remember it for the finally block
    primaryException = t;
    throw t;
} finally {
    if (primaryException == null) {
        // If no exception was thrown so far, exceptions thrown in close() will
        // not be caught and therefore be passed on to the enclosing code.
        stream.close();
    } else {
        // If an exception has already been thrown, any exception thrown in
        // close() will be suppressed as it is likely to be related to the
        // previous exception. The suppressed exception can be retrieved
        // using primaryException.getSuppressed().
        try {
            stream.close();
        } catch (Throwable suppressedException) {
            primaryException.addSuppressed(suppressedException);
        }
    }
}

(De JLS geeft aan dat de werkelijke variabelen t en primaryException onzichtbaar zijn voor de normale Java-code.)

De verbeterde vorm van try-with-resources wordt gespecificeerd als een gelijkwaardigheid met de basisvorm. Bijvoorbeeld:

try (PrintStream stream = new PrintStream(fileName)) {
    stream.println("Hello world!");
} catch (NullPointerException ex) {
    System.err.println("Null filename");
} finally {
    System.err.println("All done");    
}

is gelijk aan:

try {
    try (PrintStream stream = new PrintStream(fileName)) {
        stream.println("Hello world!");
    }
} catch (NullPointerException ex) {
    System.err.println("Null filename");
} finally {
    System.err.println("All done");    
}    

Stacktraces maken en lezen

Wanneer een uitzonderingsobject wordt gemaakt (dwz wanneer u het new maakt), legt de Throwable constructor informatie vast over de context waarin de uitzondering is gemaakt. Later kan deze informatie worden uitgevoerd in de vorm van een stacktrace, die kan worden gebruikt om te helpen bij het diagnosticeren van het probleem dat in de eerste plaats de uitzondering heeft veroorzaakt.

Stacktrace afdrukken

Een stacktrace afdrukken is gewoon een kwestie van de methode printStackTrace() aanroepen. Bijvoorbeeld:

try {
    int a = 0;
    int b = 0;
    int c = a / b;
} catch (ArithmeticException ex) {
    // This prints the stacktrace to standard output
    ex.printStackTrace();
}

De methode printStackTrace() zonder argumenten wordt afgedrukt naar de standaarduitvoer van de toepassing; dat wil zeggen de huidige System.out . Er zijn ook printStackTrace(PrintStream) en printStackTrace(PrintWriter) overbelastingen die afdrukken naar een opgegeven Stream of Writer .

Opmerkingen:

  1. De stacktrace bevat niet de details van de uitzondering zelf. U kunt de methode toString() gebruiken om die details op te halen; bv

       // Print exception and stacktrace
       System.out.println(ex);
       ex.printStackTrace();
    
  2. Stacktrace-afdrukken moeten spaarzaam worden gebruikt; zie Valkuil - Overmatige of ongepaste stacktraces . Het is vaak beter om een logboekraamwerk te gebruiken en het te loggen uitzonderingsobject door te geven.

Een stacktrace begrijpen

Overweeg het volgende eenvoudige programma dat bestaat uit twee klassen in twee bestanden. (We hebben de bestandsnamen en toegevoegde regelnummers ter illustratie getoond.)

File: "Main.java"
1   public class Main {
2       public static void main(String[] args) {
3           new Test().foo();
4       }
5   }

File: "Test.java"
1   class Test {
2       public void foo() {
3           bar();
4       }
5   
6       public int bar() {
7           int a = 1;
8           int b = 0;
9           return a / b;
10      }

Wanneer deze bestanden worden gecompileerd en uitgevoerd, krijgen we de volgende uitvoer.

Exception in thread "main" java.lang.ArithmeticException: / by zero
        at Test.bar(Test.java:9)
        at Test.foo(Test.java:3)
        at Main.main(Main.java:3)

Laten we deze regel één voor één lezen om erachter te komen wat het ons vertelt.

Regel # 1 vertelt ons dat de thread met de naam "main" is beëindigd vanwege een niet-ingevangen uitzondering. De volledige naam van de uitzondering is java.lang.ArithmeticException en het uitzonderingsbericht is "/ by zero".

Als we de javadocs opzoeken voor deze uitzondering, staat er:

Gegooid wanneer een uitzonderlijke rekenkundige situatie heeft plaatsgevonden. Een geheel getal "delen door nul" gooit bijvoorbeeld een instantie van deze klasse.

Het bericht "/ by zero" is inderdaad een sterke hint dat de oorzaak van de uitzondering is dat sommige code heeft geprobeerd iets door nul te delen. Maar wat?

De resterende 3 lijnen zijn de stapeltracering. Elke regel staat voor een methode (of constructor) -oproep in de call-stack en elke lijn vertelt ons drie dingen:

  • de naam van de klasse en methode die werd uitgevoerd,
  • de broncode bestandsnaam,
  • het broncoderegelnummer van de instructie die werd uitgevoerd

Deze regels van een stacktrace worden bovenaan weergegeven met het frame voor het huidige gesprek. Het bovenste frame in ons voorbeeld hierboven bevindt zich in de methode Test.bar en op regel 9 van het bestand Test.java. Dat is de volgende regel:

    return a / b;

Als we eerder in het bestand een paar regels kijken naar waar b is geïnitialiseerd, is het duidelijk dat b de waarde nul zal hebben. We kunnen zonder enige twijfel zeggen dat dit de oorzaak van de uitzondering is.

Als we verder moesten gaan, kunnen we aan de stacktrace zien dat bar() werd aangeroepen vanuit foo() op regel 3 van Test.java en dat foo() op zijn beurt werd aangeroepen vanuit Main.main() .

Opmerking: De namen van klassen en methoden in de stapelframes zijn de interne namen voor de klassen en methoden. U moet de volgende ongebruikelijke gevallen herkennen:

  • Een geneste of binnenklasse ziet eruit als "OuterClass $ InnerClass".
  • Een anonieme binnenklasse ziet eruit als "OuterClass $ 1", "OuterClass $ 2", enzovoort.
  • Wanneer code in een constructor, instantie veld initialisatie of een instantie initialisatie blok wordt uitgevoerd, zal de methode naam "" zijn.
  • Wanneer code in een statisch veld-initialisatie- of statisch initialisatieblok wordt uitgevoerd, zal de methode-naam "" zijn.

(In sommige versies van Java detecteert en verwijdert de stacktrace-opmaakcode herhaalde stackframesequenties, zoals kan gebeuren wanneer een toepassing mislukt vanwege overmatige recursie.)

Uitzondering chaining en geneste stacktraces

Java SE 1.4

Uitzonderingsketening vindt plaats wanneer een stuk code een uitzondering krijgt en vervolgens een nieuwe maakt en gooit, waarbij de eerste uitzondering als oorzaak wordt opgegeven. Hier is een voorbeeld:

File: Test,java
1   public class Test {
2      int foo() {
3           return 0 / 0;
4      }
5
6       public Test() {
7           try {
8               foo();
9           } catch (ArithmeticException ex) {
10              throw new RuntimeException("A bad thing happened", ex);
11          }
12      }
13
14      public static void main(String[] args) {
15          new Test();
16      }
17  }

Wanneer de bovenstaande klasse is gecompileerd en uitgevoerd, krijgen we de volgende stacktrace:

Exception in thread "main" java.lang.RuntimeException: A bad thing happened
        at Test.<init>(Test.java:10)
        at Test.main(Test.java:15)
Caused by: java.lang.ArithmeticException: / by zero
        at Test.foo(Test.java:3)
        at Test.<init>(Test.java:8)
        ... 1 more

De stacktrace begint met de klassenaam, methode en call-stack voor de uitzondering dat (in dit geval) de toepassing crashte. Dit wordt gevolgd door een regel 'Veroorzaakt door:' die de cause meldt. De klassenaam en het bericht worden gerapporteerd, gevolgd door de stapelframes van de oorzaakuitzondering. De trace eindigt met een "... N meer" wat aangeeft dat de laatste N frames hetzelfde zijn als voor de vorige uitzondering.

De "Veroorzaakt door:" wordt alleen opgenomen in de uitvoer als de cause de primaire uitzondering niet null ). Uitzonderingen kunnen voor onbepaalde tijd worden gekoppeld, en in dat geval kan de stacktrace meerdere "Caused by:" -sporen hebben.

Let op: de cause mechanisme werd alleen blootgesteld in de Throwable API in Java 1.4.0. Voordien moest uitzonderingsketen worden geïmplementeerd door de toepassing met behulp van een aangepast uitzonderingsveld om de oorzaak te vertegenwoordigen, en een aangepaste printStackTrace methode.

Een stacktrace vastleggen als een tekenreeks

Soms moet een applicatie een stacktrace als Java String kunnen vastleggen, zodat deze voor andere doeleinden kan worden gebruikt. De algemene aanpak om dit te doen, is om een tijdelijke OutputStream of Writer die naar een geheugenbuffer schrijft en die doorgeeft aan de printStackTrace(...) .

De Apache Commons- en Guava- bibliotheken bieden hulpprogramma's voor het vastleggen van een stacktrace als een String:

org.apache.commons.lang.exception.ExceptionUtils.getStackTrace(Throwable)

com.google.common.base.Throwables.getStackTraceAsString(Throwable)

Als u geen externe bibliotheken in uw codebasis kunt gebruiken, voert u de volgende methode uit met de taak:

   /**
     * Returns the string representation of the stack trace.
     *
     * @param throwable the throwable
     * @return the string.
     */
    public static String stackTraceToString(Throwable throwable) {
        StringWriter stringWriter = new StringWriter();
        throwable.printStackTrace(new PrintWriter(stringWriter));
        return stringWriter.toString();
    }

Merk op dat als u de stacktrace wilt analyseren, het eenvoudiger is om getStackTrace() en getCause() dan te proberen een stacktrace te parseren.

Omgaan met onderbroken uitzondering

InterruptedException is een verwarrend beest - het verschijnt op schijnbaar onschuldige methoden zoals Thread.sleep() , maar onjuist omgaan leidt tot moeilijk te beheren code die zich slecht gedraagt in gelijktijdige omgevingen.

In het eenvoudigste geval betekent een InterruptedException dat iemand, ergens, Thread.interrupt() op de thread waar uw code momenteel in draait. U bent misschien geneigd te zeggen: "Het is mijn code! Ik zal het nooit onderbreken!" " en doe daarom zoiets als dit:

// Bad. Don't do this.
try {
  Thread.sleep(1000);
} catch (InterruptedException e) {
  // disregard
}

Maar dit is precies de verkeerde manier om een "onmogelijke" gebeurtenis aan te pakken. Als u weet dat uw toepassing nooit een InterruptedException uitzondering zal tegenkomen, moet u een dergelijke gebeurtenis als een ernstige schending van de aannames van uw programma behandelen en zo snel mogelijk afsluiten.

De juiste manier om een "onmogelijke" onderbreking aan te pakken is als volgt:

// When nothing will interrupt your code
try {
  Thread.sleep(1000);
} catch (InterruptedException e) {
  Thread.currentThread().interrupt();
  throw new AssertionError(e);
}

Dit doet twee dingen; het herstelt eerst de interrupt-status van de thread (alsof de InterruptedException niet in de eerste plaats is gegooid), en vervolgens wordt een AssertionError gegenereerd die aangeeft dat de basisinvarianten van uw toepassing zijn geschonden. Als je zeker weet dat je de thread waarin deze code draait nooit zult onderbreken, is dit veilig omdat het catch blok nooit zou moeten worden bereikt.

Het gebruik van de klasse Uninterruptibles Guava helpt dit patroon te vereenvoudigen; aanroepen van Uninterruptibles.sleepUninterruptibly() negeert de onderbroken status van een thread totdat de slaapduur is verstreken (op welk punt het wordt hersteld voor latere oproepen om te inspecteren en hun eigen InterruptedException gooien). Als je weet dat je dergelijke code nooit zult onderbreken, voorkomt dit veilig dat je je slaapgesprekken in een try-catch-blok moet wikkelen.

Vaker kunt u echter niet garanderen dat uw thread nooit zal worden onderbroken. Met name als u code schrijft die door een uitvoerder of een ander Executor wordt uitgevoerd, is het van cruciaal belang dat uw code onmiddellijk op onderbrekingen reageert, anders zal uw toepassing vastlopen of zelfs vastlopen.

In dergelijke gevallen is het over het algemeen het beste om de InterruptedException te staan de call-stack door te geven, en vervolgens een throws InterruptedException te voegen aan elke methode. Dit lijkt misschien kludgy, maar het is eigenlijk een gewenste eigenschap - de handtekeningen van uw methode geven nu aan bellers aan dat deze onmiddellijk op onderbrekingen zal reageren.

// Let the caller determine how to handle the interrupt if you're unsure
public void myLongRunningMethod() throws InterruptedException {
  ...
}

In een beperkt aantal gevallen (bv bij dwingende een methode die niet throw geen gecontroleerd uitzonderingen) kunt u de onderbroken toestand opnieuw instellen zonder een uitzondering te genereren, in de verwachting welke code de volgende keer wordt uitgevoerd om de interrupt af te handelen. Dit vertraagt de afhandeling maar onderdrukt deze niet volledig.

// Suppresses the exception but resets the interrupted state letting later code
// detect the interrupt and handle it properly.
try {
  Thread.sleep(1000);
} catch (InterruptedException e) {
  Thread.currentThread().interrupt();
  return ...; // your expectations are still broken at this point - try not to do more work.
}

De Java-uitzonderingshiërarchie - Niet-aangevinkte en aangevinkte uitzonderingen

Alle Java-uitzonderingen zijn instanties van klassen in de klassehiërarchie Uitzondering. Dit kan als volgt worden weergegeven:

  • java.lang.Throwable - Dit is de basisklasse voor alle uitzonderingsklassen. De methoden en constructors implementeren een reeks functionaliteit die alle uitzonderingen gemeen hebben.
    • java.lang.Exception - Dit is de superklasse van alle normale uitzonderingen.
      • verschillende standaard en aangepaste uitzonderingsklassen.
      • java.lang.RuntimeException - Dit is de superklasse van alle normale uitzonderingen die niet- aangevinkte uitzonderingen zijn .
        • verschillende standaard en aangepaste runtime uitzonderingsklassen.
    • java.lang.Error - Dit is de superklasse van alle "fatale fout" -uitzonderingen.

Opmerkingen:

  1. Het onderscheid tussen aangevinkte en niet-aangevinkte uitzonderingen wordt hieronder beschreven.
  2. De Throwable , Exception en RuntimeException moet als abstract worden behandeld; zie Pitfall - Throwable Throwable, Exception, Error of RuntimeException .
  3. De Error worden door de JVM gegenereerd in situaties waarin het onveilig of onverstandig zou zijn als een toepassing probeert te herstellen.
  4. Het zou onverstandig zijn om aangepaste subtypen van Throwable . Java-tools en bibliotheken kunnen ervan uitgaan dat Error en Exception de enige directe subtypen van Throwable zijn en zich misdragen als die veronderstelling onjuist is.

Gecontroleerd versus niet-aangevinkte uitzonderingen

Een van de kritieken op ondersteuning van uitzonderingen in sommige programmeertalen is dat het moeilijk is om te weten welke uitzonderingen een bepaalde methode of procedure kan veroorzaken. Aangezien een onverwerkte uitzondering ertoe kan leiden dat een programma crasht, kan dit uitzonderingen tot een bron van kwetsbaarheid maken.

De Java-taal lost dit probleem op met het gecontroleerde uitzonderingsmechanisme. Ten eerste classificeert Java uitzonderingen in twee categorieën:

  • Gecontroleerde uitzonderingen vertegenwoordigen doorgaans verwachte gebeurtenissen die een toepassing moet kunnen verwerken. IOException en zijn subtypen vertegenwoordigen bijvoorbeeld foutcondities die kunnen optreden bij I / O-bewerkingen. Voorbeelden hiervan zijn: bestand opent mislukt omdat een bestand of map niet bestaat, netwerk leest en schrijft mislukt omdat een netwerkverbinding is verbroken, enzovoort.

  • Niet-aangevinkte uitzonderingen vertegenwoordigen doorgaans onverwachte gebeurtenissen die een toepassing niet kan verwerken. Dit zijn meestal het resultaat van een fout in de toepassing.

(In het volgende verwijst "gegooid" naar elke uitzondering die expliciet (door een throw instructie) wordt gegooid of impliciet (in een mislukte dereference, type cast enzovoort). Op dezelfde manier verwijst "gepropageerd" naar een uitzondering die in een geneste oproep, en niet gevangen in die oproep. De onderstaande voorbeeldcode zal dit illustreren.)

Het tweede deel van het gecontroleerde uitzonderingsmechanisme is dat er beperkingen zijn op methoden waarbij een gecontroleerde uitzondering kan optreden:

  • Wanneer een gecontroleerde uitzondering gegenereerd en gepropageerd in een methode moet ofwel worden gevangen door de werkwijze, of opgenomen in de methode throws clausule. (De betekenis van de throws clausule wordt in dit voorbeeld beschreven .)
  • Wanneer een aangevinkte uitzondering in een initialisatieblok wordt gegooid of verspreid, moet deze het blok worden opgevangen.
  • Een aangevinkte uitzondering kan niet worden gepropageerd door een methodeaanroep in een veldinitialisatie-expressie. (Er is geen manier om een dergelijke uitzondering te vangen.)

Kortom, een aangevinkte uitzondering moet worden afgehandeld of gedeclareerd.

Deze beperkingen zijn niet van toepassing op niet-aangevinkte uitzonderingen. Dit omvat alle gevallen waarin impliciet een uitzondering wordt gegenereerd, omdat in dergelijke gevallen uitzonderingen worden uitgeschakeld.

Gecontroleerde voorbeelden van uitzonderingen

Deze codefragmenten zijn bedoeld om de aangevinkte uitzonderingsbeperkingen te illustreren. In beide gevallen tonen we een versie van de code met een compilatiefout en een tweede versie met de gecorrigeerde fout.

// This declares a custom checked exception.
public class MyException extends Exception {
    // constructors omitted.
}

// This declares a custom unchecked exception.
public class MyException2 extends RuntimeException {
    // constructors omitted.
}

Het eerste voorbeeld laat zien hoe expliciet gegooide aangevinkte uitzonderingen als "gegooid" kunnen worden aangegeven als ze niet in de methode moeten worden verwerkt.

// INCORRECT
public void methodThrowingCheckedException(boolean flag) {
    int i = 1 / 0;                // Compiles OK, throws ArithmeticException
    if (flag) {
        throw new MyException();  // Compilation error
    } else {
        throw new MyException2(); // Compiles OK
    }
}

// CORRECTED
public void methodThrowingCheckedException(boolean flag) throws MyException {
    int i = 1 / 0;                // Compiles OK, throws ArithmeticException
    if (flag) {
        throw new MyException();  // Compilation error
    } else {
        throw new MyException2(); // Compiles OK
    }
}

Het tweede voorbeeld laat zien hoe een gepropageerde gecontroleerde uitzondering kan worden aangepakt.

// INCORRECT 
public void methodWithPropagatedCheckedException() {
    InputStream is = new FileInputStream("someFile.txt");  // Compilation error
    // FileInputStream throws IOException or a subclass if the file cannot 
    // be opened.  IOException is a checked exception.
    ...
}

// CORRECTED (Version A) 
public void methodWithPropagatedCheckedException() throws IOException {
    InputStream is = new FileInputStream("someFile.txt"); 
    ...
}

// CORRECTED (Version B) 
public void methodWithPropagatedCheckedException() {
    try {
        InputStream is = new FileInputStream("someFile.txt"); 
        ...
    } catch (IOException ex) {
        System.out.println("Cannot open file: " + ex.getMessage());
    }
}

Het laatste voorbeeld laat zien hoe om te gaan met een aangevinkte uitzondering in een statisch veld initialisatie.

// INCORRECT
public class Test {
    private static final InputStream is = 
            new FileInputStream("someFile.txt");  // Compilation error
}

// CORRECTED
public class Test {
    private static final InputStream is;
    static {
        InputStream tmp = null;
        try {
            tmp = new FileInputStream("someFile.txt");
        } catch (IOException ex) {
            System.out.println("Cannot open file: " + ex.getMessage());
        }
        is = tmp;
    }
}

Merk op dat in dit laatste geval hebben we ook te maken met de problemen die is niet kan worden om meer dan één keer toegewezen, en toch ook moet worden toegewezen, zelfs in het geval van een uitzondering.

Invoering

Uitzonderingen zijn fouten die optreden wanneer een programma wordt uitgevoerd. Overweeg het Java-programma dat twee gehele getallen verdeelt.

class Division {
    public static void main(String[] args) {
 
        int a, b, result;
 
        Scanner input = new Scanner(System.in);
        System.out.println("Input two integers");
 
        a = input.nextInt();
        b = input.nextInt();
 
        result = a / b;
 
        System.out.println("Result = " + result);
    }
}

Nu compileren en voeren we de bovenstaande code uit en zien we de uitvoer voor een poging tot deling door nul:

Input two integers
7 0
Exception in thread "main" java.lang.ArithmeticException: / by zero 
    at Division.main(Disivion.java:14)

Deling door nul is een ongeldige bewerking die een waarde zou opleveren die niet als een geheel getal kan worden weergegeven. Java lost dit op door een uitzondering te maken . In dit geval is de uitzondering een instantie van de klasse ArithmeticException .

Opmerking: het voorbeeld over het maken en lezen van stapelsporen legt uit wat de uitvoer na de twee getallen betekent.

Het nut van een uitzondering is de stroomregeling die het toestaat. Zonder uitzonderingen te gebruiken, kan een typische oplossing voor dit probleem zijn om eerst te controleren of b == 0 :

class Division {
    public static void main(String[] args) {
 
        int a, b, result;

        Scanner input = new Scanner(System.in);
        System.out.println("Input two integers");
 
        a = input.nextInt();
        b = input.nextInt();
 
        if (b == 0) {
            System.out.println("You cannot divide by zero.");
            return;
        }

        result = a / b;
 
        System.out.println("Result = " + result);
    }
}

Hiermee wordt het bericht You cannot divide by zero. afgedrukt You cannot divide by zero. naar de console en sluit het programma op een sierlijke manier wanneer de gebruiker probeert te delen door nul. Een vergelijkbare manier om dit probleem via uitzonderingsafhandeling aan te pakken, is de if flow-regeling te vervangen door een try-catch blok:

...

a = input.nextInt();
b = input.nextInt();
 
try {
    result = a / b;
}
catch (ArithmeticException e) {
    System.out.println("An ArithmeticException occurred. Perhaps you tried to divide by zero.");
    return;
}
 
...  

Een try-catch-blok wordt als volgt uitgevoerd:

  1. Begin met het uitvoeren van de code in het try blok.
  2. Als er een uitzondering optreedt in het try-blok, moet u onmiddellijk afbreken en controleren of deze uitzondering wordt gevangen door het catch blok (in dit geval wanneer de uitzondering een instantie van ArithmeticException ).
  3. Als de uitzondering wordt gevangen , wordt deze toegewezen aan de variabele e en wordt het catch uitgevoerd.
  4. Als het try of catch blok is voltooid (er zijn dus geen niet-gevangen uitzonderingen tijdens het uitvoeren van de code), ga dan door met het uitvoeren van de code onder het try-catch blok.

Het wordt over het algemeen als een goede praktijk beschouwd om uitzonderingsverwerking te gebruiken als onderdeel van de normale stroomregeling van een toepassing waarbij gedrag anders niet gedefinieerd of onverwacht zou zijn. In plaats van null retourneren wanneer een methode faalt, is het meestal beter om een uitzondering te maken, zodat de toepassing die de methode gebruikt, zijn eigen stroombesturing voor de situatie kan definiëren via uitzonderingsbehandeling van het hierboven geïllustreerde type. In zekere zin komt dit neer op het probleem van het moeten retourneren van een bepaald type , omdat een van de vele soorten uitzonderingen kan worden gegenereerd om het specifieke probleem aan te geven dat zich heeft voorgedaan.

Raadpleeg Java Pitfalls - Uitzonderingsgebruik voor meer advies over hoe en hoe u geen uitzonderingen kunt gebruiken

Retourneer instructies in try-catch-blok

Hoewel het een slechte gewoonte is, is het mogelijk om meerdere retourinstructies toe te voegen in een uitzonderingsafhandelingsblok:

 public static int returnTest(int number){
    try{
        if(number%2 == 0) throw new Exception("Exception thrown");
        else return x;
    }
    catch(Exception e){
        return 3;
    }
    finally{
        return 7;
    }
}

Deze methode retourneert altijd 7, omdat het laatste blok dat is gekoppeld aan het try / catch-blok wordt uitgevoerd voordat er iets wordt geretourneerd. Nu, zoals eindelijk return 7; , vervangt deze waarde de try / catch-retourwaarden.

Als het catch-blok een primitieve waarde retourneert en die primitieve waarde vervolgens in het slotblok wordt gewijzigd, wordt de in het catch-blok geretourneerde waarde geretourneerd en worden de wijzigingen van het slotblok genegeerd.

In het onderstaande voorbeeld wordt "0" afgedrukt, niet "1".

public class FinallyExample {

    public static void main(String[] args) {
        int n = returnTest(4);
        
        System.out.println(n);
    }

    public static int returnTest(int number) {
        
        int returnNumber = 0; 
        
        try {
            if (number % 2 == 0)
                throw new Exception("Exception thrown");
            else
                return returnNumber;
        } catch (Exception e) {
            return returnNumber;
        } finally {
            returnNumber = 1;
        }
    }
}

Geavanceerde functies van uitzonderingen

Dit voorbeeld behandelt enkele geavanceerde functies en use-cases voor uitzonderingen.

De callstack programmatisch onderzoeken

Java SE 1.4

Het primaire gebruik van uitzonderingsstacktraces is het verschaffen van informatie over een toepassingsfout en de context ervan, zodat de programmeur het probleem kan diagnosticeren en oplossen. Soms kan het voor andere dingen worden gebruikt. Een klasse SecurityManager moet bijvoorbeeld de oproepstapel onderzoeken om te bepalen of de code waarmee wordt gebeld, vertrouwd moet worden.

U kunt uitzonderingen gebruiken om de call-stack als volgt te onderzoeken:

    Exception ex = new Exception();   // this captures the call stack
    StackTraceElement[] frames = ex.getStackTrace();
    System.out.println("This method is " + frames[0].getMethodName());
    System.out.println("Called from method " + frames[1].getMethodName());

Hier zijn enkele belangrijke kanttekeningen bij:

  1. De informatie die beschikbaar is in een StackTraceElement is beperkt. Er is niet meer informatie beschikbaar dan wordt weergegeven door printStackTrace . (De waarden van de lokale variabelen in het frame zijn niet beschikbaar.)

  2. De javadocs voor getStackTrace() geven aan dat een JVM frames mag weglaten:

    Sommige virtuele machines kunnen onder bepaalde omstandigheden een of meer stapelframes weglaten uit de stapeltracering. In het extreme geval mag een virtuele machine die geen stack trace-informatie heeft over deze throwable een zero-length array retourneren van deze methode.

Optimalisatie van uitzonderingsconstructie

Zoals elders vermeld, is het construeren van een uitzondering vrij duur omdat het inhoudt dat informatie over alle stapelframes op de huidige thread wordt vastgelegd en vastgelegd. Soms weten we dat die informatie nooit zal worden gebruikt voor een bepaalde uitzondering; bijv. de stacktrace wordt nooit afgedrukt. In dat geval is er een implementatietruc die we kunnen gebruiken in een aangepaste uitzondering om te zorgen dat de informatie niet wordt vastgelegd.

De stapelframe-informatie die nodig is voor stacktraces, wordt vastgelegd wanneer de Throwable constructors de methode Throwable.fillInStackTrace() aanroepen. Deze methode is public , wat betekent dat een subklasse deze kan overschrijven. De kunst is om de geërfde methode van Throwable door een methode die niets doet; bv

  public class MyException extends Exception {
      // constructors

      @Override 
      public void fillInStackTrace() {
          // do nothing
      }
  }

Het probleem met deze benadering is dat een uitzondering die fillInStackTrace() overschrijft, nooit de stacktrace kan vastleggen en nutteloos is in scenario's waarin u er een nodig hebt.

Stacktrace wissen of vervangen

Java SE 1.4

In sommige situaties bevat de stacktrace voor een uitzondering die op de normale manier is gemaakt, onjuiste informatie of informatie die de ontwikkelaar niet aan de gebruiker wil onthullen. Voor deze scenario's kan de Throwable.setStackTrace worden gebruikt om de reeks StackTraceElement objecten te vervangen die de informatie bevat.

Het volgende kan bijvoorbeeld worden gebruikt om de stapelinformatie van een uitzondering te verwijderen:

 exception.setStackTrace(new StackTraceElement[0]);

Onderdrukte uitzonderingen

Java SE 7

Java 7 introduceerde de try-with-resources- constructie en het bijbehorende concept van uitzonderingsonderdrukking. Overweeg het volgende fragment:

try (Writer w = new BufferedWriter(new FileWriter(someFilename))) {
    // do stuff
    int temp = 0 / 0;    // throws an ArithmeticException
}

Wanneer de uitzondering wordt gegenereerd, roept de try close() op de w die alle gebufferde uitvoer doorspoelt en vervolgens de FileWriter . Maar wat gebeurt er als een IOException wordt gegenereerd tijdens het doorspoelen van de uitvoer?

Wat er gebeurt, is dat elke uitzondering die wordt gegenereerd tijdens het opschonen van een bron, wordt onderdrukt . De uitzondering wordt gevangen en toegevoegd aan de lijst met onderdrukte uitzonderingen van de primaire uitzondering. Vervolgens gaan de try-with-resources verder met het opschonen van de andere resources. Ten slotte wordt de primaire uitzondering opnieuw ingevoerd.

Een soortgelijk patroon treedt op als een uitzondering wordt gegenereerd tijdens de initialisatie van de resource of als het try blok normaal wordt voltooid. De eerste uitzondering die wordt gegenereerd, wordt de primaire uitzondering en volgende uitzonderingen die voortvloeien uit het opschonen worden onderdrukt.

De onderdrukte uitzonderingen kunnen worden opgehaald uit het primaire uitzonderingsobject door getSuppressedExceptions aan te roepen.

De try-eindelijk en try-catch-eindelijk verklaringen

De try...catch...finally instructie combineert exception handling met clean-up code. De finally blok bevat code die in alle omstandigheden zal worden uitgevoerd. Dit maakt ze geschikt voor resourcebeheer en andere soorten opschoning.

Try-slot

Hier is een voorbeeld van de eenvoudigere ( try...finally ) vorm:

try {
    doSomething();  
} finally {
    cleanUp();
}

Het gedrag van de try...finally is als volgt:

  • De code in het try blok wordt uitgevoerd.
  • Als er geen uitzondering is opgetreden in het try blok:
    • De code in de finally blok wordt uitgevoerd.
    • Indien de finally blok een uitzondering wordt deze uitzondering gepropageerd.
    • Anders gaat de controle door naar de volgende verklaring na de try...finally .
  • Als een uitzondering is opgetreden in het try-blok:
    • De code in de finally blok wordt uitgevoerd.
    • Indien de finally blok een uitzondering wordt deze uitzondering gepropageerd.
    • Anders blijft de oorspronkelijke uitzondering zich verspreiden.

De code binnen finally blok zal altijd worden uitgevoerd. (De enige uitzonderingen zijn wanneer System.exit(int) wordt genoemd, of de JVM paniek.) Aldus is een finally blok de juiste plaatscode die altijd moet worden uitgevoerd; bijv. bestanden en andere bronnen sluiten of vergrendelingen vrijgeven.

try-catch-slot

Ons tweede voorbeeld laat zien hoe catch en finally kunnen worden gebruikt. Het illustreert ook dat het opruimen van middelen niet eenvoudig is.

// This code snippet writes the first line of a file to a string
String result = null;
Reader reader = null;
try {
    reader = new BufferedReader(new FileReader(fileName));
    result = reader.readLine();
} catch (IOException ex) {
    Logger.getLogger.warn("Unexpected IO error", ex);  // logging the exception
} finally {
    if (reader != null) {
        try {
            reader.close();
        } catch (IOException ex) {
            // ignore / discard this exception
        }
    }
}

De complete set van (hypothetische) gedragingen van try...catch...finally in dit voorbeeld te ingewikkeld om hier te beschrijven. De eenvoudige versie is dat de code in het finally altijd wordt uitgevoerd.

Kijkend vanuit het perspectief van resource management:

  • Wij verklaren dat de "bron" (dwz reader variabel) voor de try blok, zodat het zal in ruimte voor de finally te blokkeren.
  • Door de new FileReader(...) , kan de catch elke IOError uitzondering verwerken die wordt gegenereerd bij het openen van het bestand.
  • We hebben een reader.close() in het finally te blokkeren omdat er een aantal uitzondering paden die we niet kunnen onderscheppen, hetzij in de try blok of in catch -blok.
  • Omdat echter een uitzondering had kunnen worden gegooid voordat reader is geïnitialiseerd, hebben we ook een expliciete behoefte null test.
  • Ten slotte kan de reader.close() (hypothetisch) een uitzondering veroorzaken. Dat kan ons niet schelen, maar als we de uitzondering bij de bron niet opvangen, zouden we het verderop in de call-stack moeten aanpakken.
Java SE 7

Java 7 en hoger bieden een alternatieve syntaxis van try-with-resources die het opschonen van bronnen aanzienlijk vereenvoudigt.

De 'gooit'-clausule in een methodeverklaring

Het gecontroleerde uitzonderingsmechanisme van Java vereist dat de programmeur verklaart dat bepaalde methoden gespecificeerde gecontroleerde uitzonderingen kunnen genereren. Dit wordt gedaan met behulp van de throws clausule. Bijvoorbeeld:

public class OddNumberException extends Exception { // a checked exception
}

public void checkEven(int number) throws OddNumberException {
    if (number % 2 != 0) {
        throw new OddNumberException();
    }
}

De throws OddNumberException verklaart dat een oproep naar checkEven een uitzondering dat is van het type zou kunnen gooien OddNumberException .

Een throws clausule kan een lijst met typen declareren en kan niet-aangevinkte uitzonderingen en aangevinkte uitzonderingen bevatten.

public void checkEven(Double number) 
        throws OddNumberException, ArithmeticException {
    if (!Double.isFinite(number)) {
        throw new ArithmeticException("INF or NaN");
    } else if (number % 2 != 0) {
        throw new OddNumberException();
    }
}

Wat heeft het voor zin om niet-aangevinkte uitzonderingen als geworpen te verklaren?

De throws clausule in een methodeverklaring dient twee doelen:

  1. Het vertelt de compiler welke uitzonderingen worden gegenereerd, zodat de compiler niet-ingevangen (aangevinkte) uitzonderingen als fouten kan melden.

  2. Het vertelt een programmeur die code schrijft die de methode aanroept welke uitzonderingen hij kan verwachten. Voor dit doel maakt het zintuigen vaak onaangekondigde uitzonderingen in een throws .

Opmerking: dat de throws ook wordt gebruikt door de javadoc-tool bij het genereren van API-documentatie en door de typische "hover text" methode-tips van IDE.

Gooien en methode negeren

De throws clausule maakt deel uit van de handtekening van een methode met het doel de methode te negeren. Een vervangingsmethode kan worden gedeclareerd met dezelfde set aangevinkte uitzonderingen die worden gegenereerd door de overschreven methode, of met een subset. De opheffingsmethode kan echter geen extra aangevinkte uitzonderingen toevoegen. Bijvoorbeeld:

@Override
public void checkEven(int number) throws NullPointerException // OK—NullPointerException is an unchecked exception
    ...

@Override
public void checkEven(Double number) throws OddNumberException // OK—identical to the superclass
    ...

class PrimeNumberException extends OddNumberException {}
class NonEvenNumberException extends OddNumberException {}

@Override
public void checkEven(int number) throws PrimeNumberException, NonEvenNumberException // OK—these are both subclasses

@Override
public void checkEven(Double number) throws IOExcepion         // ERROR

De reden voor deze regel is dat als een overschreven methode een aangevinkte uitzondering kan genereren en de overschreven methode niet kan gooien, dat de substitueerbaarheid van het type zou verbreken.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow