Sök…


Introduktion

Objekt av typen Throwable och dess subtyper kan skickas upp i bunten med nyckelordet throw och fångas med try…catch uttalanden.

Syntax

  • void someMethod () kastar SomeException {} // metoddeklaration, tvingar metoduppringare att fånga om SomeException är en kontrollerad undantagstyp

  • Prova {

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

    }

  • fånga (SomeException e) {

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

    }

  • till sist {

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

    }

Att få ett undantag med try-catch

Ett undantag kan fångas och hanteras med try...catch . (I själva verket try uttalanden ta andra former, som beskrivs i andra exempel om try...catch...finally och try-with-resources .)

Försök med ett fångstblock

Den enklaste formen ser ut så här:

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

Uppförandet av ett enkelt try...catch är som följer:

  • Uttalandena i try blocket exekveras.
  • Om inget undantag kastas av uttalandena i try , övergår kontrollen till nästa uttalande efter try...catch .
  • Om ett undantag kastas i try block.
    • Undantagsobjektet testas för att se om det är ett exempel på SomeException eller en subtyp.
    • Om det är, då catch blocket kommer att fånga undantag:
      • Variabeln e är bunden till undantagsobjektet.
      • Koden i catch exekveras.
      • Om den koden kastar ett undantag, förökas det nyligen kastade undantaget i stället för det ursprungliga.
      • Annars övergår kontrollen till nästa uttalande efter try...catch .
    • Om det inte är det, fortsätter det ursprungliga undantaget att spridas.

Försök med flera fångster

Ett try...catch kan också ha flera catch . Till exempel:

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

Om det finns flera catch försöks de en i taget från och med den första tills en matchning hittas för undantaget. Motsvarande hanterare körs (som ovan) och kontrollen överförs sedan till nästa uttalande efter try...catch . catch blockerar efter den som matchar alltid hoppas över, även om hanterarkoden kastar ett undantag .

Matchningsstrategin "top down" har konsekvenser för fall där undantagen i catch inte är osammanhängande. Till exempel:

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

Detta kodavsnitt kommer att mata ut "Undantag" snarare än "RuntimeException". Eftersom RuntimeException är en subtyp av Exception kommer den första (mer allmänna) catch att matchas. Den andra (mer specifika) catch kommer aldrig att utföras.

Lärdomarna att lära av detta är att de mest specifika catch (när det gäller undantagstyperna) ska visas först och de mest allmänna bör vara sista. (Vissa Java-kompilatorer varnar dig om en catch aldrig kan köras, men detta är inte ett sammanställningsfel.)

Fångstblock med flera undantag

Java SE 7

Från och med Java SE 7 kan ett enda catch hantera en lista med icke relaterade undantag. Undantagstypen listas, separerade med en vertikal stapel ( | ) -symbol. Till exempel:

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

Uppförandet av en fångst med flera undantag är en enkel förlängning för fallet med ett enda undantag. catch matchar om det kastade undantaget matchar (åtminstone) ett av de listade undantagen.

Det finns en del extra subtilitet i specifikationen. Typen av e är en syntetisk förening av undantagstyperna i listan. När värdet på e används är dess statiska typ den minst vanliga supertypen av typunionen. Om e återdras inom catch , är de undantagstyper som kastas typerna i facket. Till exempel:

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

I ovanstående IOException och SQLException undantag vars minst vanliga supertyp är Exception . Detta innebär att report måste matcha report(Exception) . Men kompilatorn vet att throw kan kasta en IOException eller en SQLException . Således kan method förklaras som throws IOException, SQLException snarare än throws Exception . (Vilket är bra: se Fallgrop - Kasta kasta, undantag, fel eller RuntimeException .)

Kasta ett undantag

Följande exempel visar grunderna för att kasta ett undantag:

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

Undantaget kastas på den tredje raden. Detta uttalande kan delas upp i två delar:

  • new IllegalArgumentException(...) skapar en instans av klassen IllegalArgumentException , med ett meddelande som beskriver det fel som undantaget rapporterar.

  • throw ... kastar sedan undantagsobjektet.

När undantaget kastas gör det att de bifogade uttalandena upphör onormalt tills undantaget hanteras . Detta beskrivs i andra exempel.

Det är bra att både skapa och kasta undantagsobjektet i ett enda uttalande, som visas ovan. Det är också bra att inkludera ett meningsfullt felmeddelande i undantag för att hjälpa programmeraren att förstå orsaken till problemet. Detta är dock inte nödvändigtvis meddelandet som du bör visa till slutanvändaren. (Till att börja med har Java inget direkt stöd för internationalisering av undantagsmeddelanden.)

Det finns ytterligare ett par punkter att göra:

  • Vi har förklarat checkNumber som throws IllegalArgumentException . Detta var inte strikt nödvändigt eftersom IllegalArgumentException är ett kontrollerat undantag. se Java-undantagshierarkin - Okonterade och kontrollerade undantag . Det är emellertid god praxis att göra detta, och även att inkludera de undantag som kastas en metods javadoc-kommentarer.

  • Koden omedelbart efter ett throw nås . Därför om vi skrev detta:

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

    kompilatorn skulle rapportera en sammanställning fel för return uttalande.

Undantag kedja

Många standard undantag har en konstruktör med en andra cause argument utöver det vanliga message argument. cause låter dig kedja undantag. Här är ett exempel.

Först definierar vi ett okontrollerat undantag som vår applikation kommer att kasta när den stöter på ett icke-återvinningsbart fel. Observera att vi har inkluderat en konstruktör som accepterar en 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);
        }
    }

Nästa, här är en kod som illustrerar undantagskedjan.

    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);
        }
    }

Den throw inom try blocket upptäcker ett problem och rapporterar det via ett undantag med ett enkelt budskap. Däremot throw i catch är blocket hanterar IOException genom att linda den i ett nytt (markerad) undantag. Det kastar dock inte bort det ursprungliga undantaget. Genom att överföra IOException som cause registrerar vi den så att den kan skrivas ut i stacktrace, vilket förklaras i Skapa och läsa stackspår .

Anpassade undantag

Under de flesta förhållanden är det enklare från koddesignsynpunkt att använda befintliga generiska Exception när man kastar undantag. Detta gäller särskilt om du bara behöver undantaget för att ha ett enkelt felmeddelande. I så fall är RuntimeException vanligtvis att föredra, eftersom det inte är ett kontrollerat undantag. Andra undantagsklasser finns för vanliga felklasser:

Fall där du vill använda en anpassad undantag klass inkluderar följande:

  • Du skriver ett API eller bibliotek för användning av andra, och du vill tillåta användare av ditt API att specifikt kunna fånga och hantera undantag från ditt API och kunna skilja dessa undantag från andra, mer generiska undantag .
  • Du kastar undantag för en viss typ av fel i en del av ditt program, som du vill fånga och hantera i en annan del av ditt program, och du vill kunna skilja dessa fel från andra, mer generiska fel.

Du kan skapa dina egna anpassade undantag genom att utöka RuntimeException för ett okontrollerat undantag, eller kontrollera undantag genom att utvidga alla Exception som inte också är underklass för RuntimeException , eftersom:

Undantagsklasser som inte också är underklasser av RuntimeException kontrolleras undantag

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;
    }
}

Dessa kan användas precis som fördefinierade undantag:

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

Och fälten kan användas där undantaget fångas och hanteras:

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 );
    }
}

Tänk på att enligt Oracle's Java-dokumentation :

[...] Om en klient rimligen kan förväntas återhämta sig från ett undantag, gör det till ett kontrollerat undantag. Om en klient inte kan göra något för att återhämta sig från undantaget, gör det till ett okontrollerat undantag.

Mer:

Try-with-resources-uttalandet

Java SE 7

Som exemplet med try-catch-final-uttalandet illustrerar, kräver rensning av resurser med en finally klausul en betydande mängd "pannplatta" -kod för att implementera kantfallen korrekt. Java 7 tillhandahåller ett mycket enklare sätt att hantera detta problem i form av försök med resurser .

Vad är en resurs?

Java 7 introducerade gränssnittet java.lang.AutoCloseable för att tillåta klasser att hanteras med try-with-resources- uttalandet. Instanser av klasser som implementerar AutoCloseable kallas resurser . Dessa måste vanligtvis bortskaffas i tid snarare än att förlita sig på skräpkollektorn för att bortskaffa dem.

Det AutoCloseable gränssnittet definierar en enda metod:

public void close() throws Exception

En close() -metod bör bortskaffa resursen på lämpligt sätt. Specifikationen anger att det bör vara säkert att anropa metoden på en resurs som redan har kasserats. Dessutom uppmuntras klasser som implementerar Autocloseable starkt att förklara metoden close() för att kasta ett mer specifikt undantag än Exception eller inget undantag alls.

Ett brett utbud av standard Java-klasser och gränssnitt implementerar AutoCloseable . Dessa inkluderar:

  • InputStream , OutputStream och deras underklasser
  • Reader , Writer och deras underklasser
  • Socket och ServerSocket och deras underklasser
  • Channel och dess underklasser, och
  • JDBC-gränssnitten Connection , Statement och ResultSet och deras underklasser.

Applikation och klasser från tredje part kan också göra det.

Det grundläggande försöket med resursuttalandet

Syntaxen för ett försök med resurser är baserat på klassiska try-catch , try-end och try-catch-äntligen former. Här är ett exempel på en "grundläggande" form; dvs formen utan catch eller finally .

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

De resurser som ska hantera deklareras som variabler i (...) avsnitt efter try klausulen. I exemplet ovan förklarar vi en stream och initialiserar den till ett nyskapat PrintStream .

När resursvariablerna har initierats, det try är blocket exekveras. När detta är klart kommer stream.close() att stream.close() automatiskt för att säkerställa att resursen inte läcker. Observera att det close() samtalet sker oavsett hur blocket slutförs.

De förbättrade försök med resursuttalningarna

Try-with-resources- uttalandet kan förbättras med catch och finally blockeringar, som med syntaxen för try-catch-slutligen före Java 7. Följande kodavsnitt lägger till ett catch till vårt tidigare för att hantera FileNotFoundException som PrintStream konstruktören kan kasta:

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");
}

Om antingen resursinitieringen eller försöksblocket kastar undantaget, kommer catch att utföras. Det finally blocket kommer alltid att exekveras, som med en konventionell try-catch-slutlig uttalande.

Det finns dock ett par saker att notera:

  • Resursvariabeln är utanför räckvidden i catch och blockerar finally .
  • Resursrensningen kommer att ske innan uttalandet försöker matcha catch .
  • Om den automatiska resursrensningen kastade ett undantag, kan det fångas i ett av catch .

Hantera flera resurser

Kodavsnitten ovan visar en enda resurs som hanteras. I själva verket kan försök med resurser hantera flera resurser i ett uttalande. Till exempel:

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

Detta beter sig som du kan förvänta dig. Både is och os stängs automatiskt i slutet av try . Det finns ett par punkter att notera:

  • Initieringarna sker i kodordningen och senare resursvariablerare kan använda värdena på de tidigare.
  • Alla resursvariabler som har initialiserats kommer att rensas upp.
  • Resursvariabler städas upp i omvänd ordning av deklarationerna.

Således, i exemplet ovan, is initialiseras innan os och städat upp efter det, och is kommer att städas upp om det finns ett undantag vid initiering os .

Likvärdighet mellan try-with-resource och klassisk try-catch-äntligen

Java Language Specification specificerar beteendet hos formulär med resursformer i termer av det klassiska try-catch-äntligen uttalandet. (Se JLS för fullständig information.)

Till exempel denna grundläggande försök med resurs :

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

definieras som likvärdigt med denna try-catch-slutligen :

// 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);
        }
    }
}

(JLS anger att de faktiska variablerna för t och primaryException är osynliga för normal Java-kod.)

Den förbättrade formen av försök med resurser anges som en ekvivalens med grundformen. Till exempel:

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

är ekvivalent med:

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");    
}    

Skapa och läsa stackspår

När ett undantagsobjekt skapas (det vill säga när du new det) fångar Throwable konstruktören information om det sammanhang där undantaget skapades. Senare kan denna information matas ut i form av en stapelspårning, som kan användas för att diagnostisera problemet som orsakade undantaget i första hand.

Skriva ut en stacktrace

Att skriva ut en stacktrace handlar helt enkelt om att kalla printStackTrace() . Till exempel:

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

printStackTrace() utan argument skrivs ut på applikationens standardutgång; dvs. det nuvarande System.out . Det finns också printStackTrace(PrintStream) och printStackTrace(PrintWriter) överbelastningar som skrivs ut till en viss Stream eller Writer .

Anmärkningar:

  1. Stacktrace inkluderar inte detaljerna i själva undantaget. Du kan använda toString() för att få dessa detaljer; t.ex

       // Print exception and stacktrace
       System.out.println(ex);
       ex.printStackTrace();
    
  2. Stacktrace-utskrift bör användas sparsamt; se Pitfall - Överdrivna eller olämpliga stacktraces . Det är ofta bättre att använda ett loggningsramverk och passera undantagsobjektet som ska loggas.

Förstå en stacktrace

Tänk på följande enkla program som består av två klasser i två filer. (Vi har visat filnamn och lagt till radnummer för illustrationsändamål.)

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      }

När dessa filer sammanställs och körs får vi följande utdata.

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)

Låt oss läsa den här raden i taget för att ta reda på vad den berättar för oss.

Rad nr 1 berättar att tråden som kallas "main" har avslutats på grund av ett oöverträffat undantag. Undantagets fulla namn är java.lang.ArithmeticException , och undantagsmeddelandet är "/ med noll".

Om vi letar upp javadokorna för detta undantag, står det:

Kasta när ett exceptionellt aritmetiskt tillstånd har inträffat. Till exempel kastar ett heltal "dividera med noll" en instans av denna klass.

I själva verket är meddelandet "/ med noll" en stark antydning att orsaken till undantaget är att någon kod har försökt dela något med noll. Men vad?

De återstående tre raderna är stapelspåret. Varje rad representerar en metod (eller konstruktör) -samtal på samtalstacken, och var och en berättar tre saker:

  • namnet på klassen och metoden som genomfördes,
  • källkodens filnamn,
  • källkodslinjenumret för uttalandet som kördes

Dessa linjer i en stapeltrappa listas med ramen för det aktuella samtalet upptill. Den översta ramen i vårt exempel ovan finns i Test.bar metoden och på rad 9 i Test.java-filen. Det är följande rad:

    return a / b;

Om vi tittar på ett par rader tidigare i filen till där b initieras är det uppenbart att b har värdet noll. Vi kan utan tvekan säga att detta är orsaken till undantaget.

Om vi behövde gå längre kan vi se från stacktrace att bar() kallades från foo() vid rad 3 i Test.java, och att foo() i sin tur kallades från Main.main() .

Obs: Klass- och metodnamnen i stapelramarna är de interna namnen för klasserna och metoderna. Du måste känna igen följande ovanliga fall:

  • En kapslad eller inre klass kommer att se ut som "OuterClass $ InnerClass".
  • En anonym inre klass kommer att se ut som "OuterClass $ 1", "OuterClass $ 2" osv.
  • När kod i en konstruktör, instansfältinitierare eller ett instansinitieringsblock exekveras kommer metodnamnet att vara "".
  • När kod i en statisk fältinitierare eller statisk initieringsblock körs kommer metodnamnet att vara "".

(I vissa versioner av Java, kommer stacktrace-formateringskoden att upptäcka och undanröja upprepade stackframesekvenser, som kan uppstå när ett program misslyckas på grund av alltför hög rekursion.)

Undantagskedjning och kapslade stapelspår

Java SE 1.4

Undantagskedjning inträffar när ett kodstycke får ett undantag och sedan skapar och kastar ett nytt och passerar det första undantaget som orsak. Här är ett exempel:

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  }

När ovanstående klass sammanställs och körs får vi följande 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

Stacktrace börjar med klassens namn, metod och samtalsstapel med undantag för att (i detta fall) fick applikationen att krascha. Detta följs av en rad "Orsakad av:" som rapporterar cause . Klassnamnet och meddelandet rapporteras, följt av orsakens undantags stapelramar. Spåret slutar med en "... N mer" som indikerar att de sista N-ramarna är desamma som för föregående undantag.

"Orsakad av:" ingår endast i utgången när det primära undantags cause inte är null ). Undantag kan kedjas på obestämd tid, och i så fall kan stapelspåret ha flera "orsakade av:" -spår.

Anmärkning: cause mekanismen endast exponeras i Throwable API i Java 1.4.0. Innan dess behövde undantagskedjor implementeras av applikationen med ett anpassat undantagsfält för att representera orsaken och en anpassad printStackTrace metod.

Fånga en stacktrace som en sträng

Ibland måste en applikation kunna fånga en stacktrace som en Java- String , så att den kan användas för andra ändamål. Den allmänna metoden för att göra detta är att skapa en tillfällig OutputStream eller Writer som skriver till en buffert i minnet och printStackTrace(...) det till printStackTrace(...) .

Apache Commons- och Guava- biblioteken tillhandahåller verktygsmetoder för att fånga en stacktrace som en sträng:

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

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

Om du inte kan använda tredjepartsbibliotek i din kodbas, gör följande metod med uppgiften:

   /**
     * 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();
    }

Observera att om din avsikt är att analysera stacktrace är det enklare att använda getStackTrace() och getCause() än att försöka analysera en stacktrace.

Hantering av InterruptException

InterruptedException är ett förvirrande djur - det dyker upp i till synes oskyldiga metoder som Thread.sleep() , men hantering av det fel leder till svårhanterlig kod som uppträder dåligt i samtidiga miljöer.

På det mest grundläggande, om en InterruptedException fångas betyder det någon, någonstans, kallad Thread.interrupt() på tråden som din kod för närvarande körs i. Du kan vara benägen att säga "Det är min kod! Jag kommer aldrig att avbryta den! " och gör därför något liknande:

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

Men detta är exakt fel sätt att hantera en "omöjlig" händelse som inträffar. Om du vet att din ansökan aldrig kommer att stöta på en InterruptedException bör du behandla en sådan händelse som en allvarlig kränkning av programmets antaganden och avsluta så snabbt som möjligt.

Det rätta sättet att hantera ett "omöjligt" avbrott är så:

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

Detta gör två saker; den återställer först avbrottsstatusen för tråden (som om InterruptedException inte hade kastats i första hand), och sedan kastar den en AssertionError indikerar att de grundläggande invarianterna i din ansökan har kränkts. Om du med säkerhet vet att du aldrig kommer att avbryta tråden som den här koden körs i här är säker eftersom catch aldrig ska nås.

Att använda Uninterruptibles klass Uninterruptibles hjälper till att förenkla detta mönster; ringer Uninterruptibles.sleepUninterruptibly() bortser från det avbrutna tillståndet för en tråd tills sömns varaktighet har gått ut (vid vilken tidpunkt det återställs för senare samtal för att inspektera och kasta sin egen InterruptedException ). Om du vet att du aldrig kommer att avbryta en sådan kod undviker du på ett säkert sätt att behöva slå in dina sömnsamtal i ett try-catch-block.

Oftare kan du dock inte garantera att din tråd aldrig kommer att avbrytas. Speciellt om du skriver kod som kommer att köras av en Executor eller någon annan trådhantering är det viktigt att din kod reagerar direkt på avbrott, annars kommer din applikation att stanna eller till och med dödläge.

I sådana fall är det bästa i allmänhet att låta InterruptedException sprida upp samlingsstacken och lägga till ett throws InterruptedException till varje metod i tur och ordning. Det här kan tyckas klumpigt men det är faktiskt en önskvärd egenskap - din metods underskrifter indikerar nu för de som ringer att den kommer att reagera snabbt på avbrott.

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

I begränsade fall (t.ex. när du åsidosätter en metod som inte throw några kontrollerade undantag) kan du återställa den avbrutna statusen utan att höja ett undantag och förvänta dig att vilken kod som ska utföras bredvid hanteringen av avbrottet. Detta försenar hanteringen av avbrottet men undertrycker inte det helt.

// 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.
}

Java-undantagshierarkin - Okonterade och kontrollerade undantag

Alla Java-undantag är förekomster av klasser i undantagsklasshierarkin. Detta kan representeras på följande sätt:

  • java.lang.Throwable - Detta är basklassen för alla undantagsklasser. Dess metoder och konstruktörer implementerar en rad funktioner som är gemensamma för alla undantag.
    • java.lang.Exception - Detta är superklassen för alla normala undantag.
      • olika standard- och anpassade undantagsklasser.
      • java.lang.RuntimeException - Detta är superklassen för alla normala undantag som är okontrollerade undantag .
        • olika standard- och anpassade undantagsklasser för runtime.
    • java.lang.Error - Detta är superklassen för alla "dödliga fel" -undantag.

Anmärkningar:

  1. Skillnaden mellan kontrollerade och okontrollerade undantag beskrivs nedan.
  2. Throwable , Exception och RuntimeException bör behandlas som abstract ; se Pitfall - Throwing Throwable, Exception, Error or RuntimeException .
  3. Error kastas av JVM i situationer där det skulle vara osäkert eller oklokt för en applikation att försöka återhämta sig.
  4. Det vore oklokt att förklara anpassade undertyper av Throwable . Java-verktyg och bibliotek kan anta att Error och Exception är de enda direkta undertyperna av Throwable , och kan Throwable om det antagandet är felaktigt.

Kontrollerat kontra Unchecked Undantag

En av kritikerna av undantagsstöd i vissa programmeringsspråk är att det är svårt att veta vilka undantag en given metod eller procedur kan kasta. Med tanke på att ett obehandlat undantag kan leda till att ett program kraschar kan detta göra undantag till en källa till bräcklighet.

Java-språket hanterar detta problem med den kontrollerade undantagsmekanismen. Först klassificerar Java undantag i två kategorier:

  • Kontrollerade undantag representerar vanligtvis förväntade händelser som en applikation bör kunna hantera. Till exempel IOException och dess undertyper felförhållanden som kan uppstå i I / O-operationer. Exempel inkluderar att filen öppnas misslyckas eftersom en fil eller katalog inte finns, nätverket läser och skriver misslyckas eftersom en nätverksanslutning har brutits och så vidare.

  • Okontrollerade undantag representerar vanligtvis oförutsedda händelser som en applikation inte kan hantera. Dessa är vanligtvis resultatet av ett fel i applikationen.

(I det följande hänvisar "kastad" till alla undantag som kastas uttryckligen (med ett throw ) eller implicit (i en misslyckad dereferens, typ avgjutning osv.). På liknande sätt avser "förökad" ett undantag som kastades i en kapslade samtal och inte fångas in i det samtalet. Exempelkoden nedan illustrerar detta.)

Den andra delen av den kontrollerade undantagsmekanismen är att det finns begränsningar för metoder där ett kontrollerat undantag kan förekomma:

  • När en markerad undantag kastas eller förökas i en metod, måste det antingen fångas av metoden, eller som anges i metodens throws klausul. (Betydelsen av den throws klausulen beskrivs i detta exempel .)
  • När ett kontrollerat undantag kastas eller sprids i ett initialiseringsblock måste det fångas blocket.
  • Ett kontrollerat undantag kan inte förökas av ett metodsamtal i ett fältinitieringsuttryck. (Det finns inget sätt att fånga ett sådant undantag.)

Kort sagt, ett kontrollerat undantag måste antingen hanteras eller deklareras.

Dessa begränsningar gäller inte för okontrollerade undantag. Detta inkluderar alla fall där ett undantag kastas implicit, eftersom alla sådana fall kastar okontrollerade undantag.

Kontrollerade undantagsexempel

Dessa kodavsnitt är avsedda att illustrera de kontrollerade undantagsbegränsningarna. I båda fallen visar vi en version av koden med ett sammanställningsfel och en andra version med felet korrigerat.

// 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.
}

Det första exemplet visar hur uttryckligen kastade kontrollerade undantag kan deklareras som "kastade" om de inte ska hanteras i metoden.

// 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
    }
}

Det andra exemplet visar hur ett utbrett kontrollerat undantag kan hanteras.

// 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());
    }
}

Det sista exemplet visar hur man hanterar ett kontrollerat undantag i en statisk fältinitierare.

// 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;
    }
}

Notera att i det senare fallet har vi också ta itu med de problem som is inte kan hänföras till mer än en gång, och ändå också tilldelas, även i fallet med ett undantag.

Introduktion

Undantag är fel som uppstår när ett program körs. Tänk på Java-programmet nedan som delar två heltal.

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 sammanställer och kör vi ovanstående kod och ser utgången för en försökt delning med noll:

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

Division med noll är en ogiltig operation som ger ett värde som inte kan representeras som ett heltal. Java hanterar detta genom att kasta ett undantag . I detta fall är undantaget ett exempel på klassen ArithmeticException .

Obs: Exemplet på att skapa och läsa stapelspår förklarar vad utdata efter de två siffrorna betyder.

Nyttan med ett undantag är flödeskontrollen som det tillåter. Utan att använda undantag kan en typisk lösning på detta problem vara att först kontrollera om 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);
    }
}

Detta skriver ut meddelandet You cannot divide by zero. till konsolen och avslutar programmet på ett graciöst sätt när användaren försöker dela med noll. Ett likvärdigt sätt att hantera detta problem via undantagshantering är att ersätta if flödeskontrollen med ett try-catch block:

...

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;
}
 
...  

Ett testfångstblock utförs enligt följande:

  1. Börja köra koden i try .
  2. Om ett undantag inträffar i försöksblocket, avbryt omedelbart och kontrollera om detta undantag fångas av catch (i detta fall när undantaget är ett exempel på ArithmeticException ).
  3. Om undantaget fångas tilldelas det variabeln e och catch körs.
  4. Om antingen try eller catch är avslutat (dvs inga oupptagna undantag inträffar under kodutförandet) fortsätter du att köra koden under try-catch blocket.

Det anses generellt som god praxis att använda undantagshantering som en del av den normala flödeskontrollen för en applikation där beteende annars skulle vara odefinierat eller oväntat. Till exempel, istället för att returnera null när en metod misslyckas, är det vanligtvis bättre praxis att kasta ett undantag så att applikationen som använder metoden kan definiera sin egen flödeskontroll för situationen genom undantagshantering av det slag som illustreras ovan. På något sätt handlar det om problemet med att behöva returnera en viss typ , eftersom ett eller flera av flera slags undantag kan kastas för att indikera det specifika problem som inträffade.

Mer information om hur och hur man inte använder undantag finns i Java Fallgropar - Undantagsanvändning

Returnera uttalanden i försök fångstblock

Även om det är dålig praxis, är det möjligt att lägga till flera returrätt i ett undantagshanteringsblock:

 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;
    }
}

Denna metod kommer alltid att returnera 7 eftersom det slutliga blocket som är associerat med try / catch blocket körs innan något returneras. Nu, som äntligen har return 7; , detta värde ersätter try / fånga returvärden.

Om fångstblocket returnerar ett primitivt värde och det primitiva värdet därefter ändras i det slutliga blocket, kommer det värde som returneras i fångstblocket att returneras och ändringarna från det slutliga blocket ignoreras.

Exemplet nedan kommer att skriva ut "0", inte "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;
        }
    }
}

Avancerade funktioner för undantag

Detta exempel omfattar några avancerade funktioner och användningsfall för undantag.

Undersökning av samtalsprogrammatiskt

Java SE 1.4

Den primära användningen av undantags stacktraces är att tillhandahålla information om ett applikationsfel och dess sammanhang så att programmeraren kan diagnostisera och fixa problemet. Ibland kan det användas för andra saker. Till exempel kan en SecurityManager klass behöva undersöka samtalstacken för att avgöra om koden som ringer ska lita på.

Du kan använda undantag för att undersöka samtalstacken programmatiskt enligt följande:

    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());

Det finns några viktiga varningar om detta:

  1. Informationen som finns tillgänglig i StackTraceElement är begränsad. Det finns ingen mer information tillgänglig än som visas av printStackTrace . (Värdena på de lokala variablerna i ramen är inte tillgängliga.)

  2. Javadokorna för getStackTrace() att en JVM är tillåten att utelämna ramar:

    Vissa virtuella maskiner kan under vissa omständigheter utelämna en eller flera stapelramar från stackspåret. I det extrema fallet är en virtuell maskin som inte har någon stapelspårinformation angående denna kastbara tillåtet att returnera en nollängdsgrupp från denna metod.

Optimera undantagskonstruktion

Som nämnts någon annanstans är att konstruera ett undantag ganska dyrt eftersom det medför att fånga och spela in information om alla stapelramar på den aktuella tråden. Ibland vet vi att den informationen aldrig kommer att användas för ett visst undantag; t.ex. stacktracket kommer aldrig att skrivas ut. I så fall finns det ett implementeringstrick som vi kan använda i ett anpassat undantag för att orsaka att informationen inte fångas.

Informationen om stapelramen som behövs för stackspår fångas in när Throwable konstruktörerna kallar Throwable.fillInStackTrace() . Denna metod är public , vilket innebär att en underklass kan åsidosätta den. Tricket är att åsidosätta metoden som ärvs från Throwable med en som inte gör något; t.ex

  public class MyException extends Exception {
      // constructors

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

Problemet med den här metoden är att ett undantag som åsidosätter fillInStackTrace() aldrig kan fånga stacktrace och är värdelöst i scenarier där du behöver en.

Radera eller byta ut stapelspåret

Java SE 1.4

I vissa situationer innehåller stacktrace för ett undantag skapat på normalt sätt antingen felaktig information eller information som utvecklaren inte vill avslöja för användaren. För dessa scenarier kan Throwable.setStackTrace användas för att ersätta matrisen med StackTraceElement objekt som innehåller informationen.

Till exempel kan följande användas för att kassera ett undantags stackinformation:

 exception.setStackTrace(new StackTraceElement[0]);

Undertryckta undantag

Java SE 7

Java 7 introducerade try-with-resources- konstruktionen och det tillhörande begreppet undantagssuppression. Tänk på följande utdrag:

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

När undantag kastas, det try att ringa close()w som kommer att spola alla buffrade utgång och stäng sedan FileWriter . Men vad händer om en IOException kastas när du spolar utmatningen?

Vad som händer är att alla undantag som kastas när man rensar upp en resurs undertrycks . Undantaget fångas och läggs till i det primära undantagets lista över undertryckta undantag. Nästa försök med resurser fortsätter med saneringen av de andra resurserna. Slutligen kommer det primära undantaget att återtröms.

Ett liknande mönster uppstår om ett undantag som det kastades under resursinitieringen, eller om try slutförs normalt. Det första undantaget som kastas blir det primära undantaget, och efterföljande som härrör från sanering undertrycks.

De undertryckta undantagen kan hämtas från det primära undantagsobjektet genom att ringa getSuppressedExceptions .

Test-äntligen och try-catch-slutligen uttalanden

try...catch...finally uttalande kombinerar undantagshantering med saneringskod. Det finally blocket innehåller kod som kommer att köras under alla omständigheter. Detta gör dem lämpliga för resurshantering och andra typer av sanering.

Try-slutligen

Här är ett exempel på den enklare ( try...finally ) formen:

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

try...finally beteende try...finally är som följer:

  • Koden i try blocket exekveras.
  • Om inget undantag kastades i try :
    • Koden i det finally blocket körs.
    • Om det finally blocket kastar ett undantag förökas detta undantag.
    • Annars övergår kontrollen till nästa uttalande efter try...finally .
  • Om ett undantag kastades i försöksblocket:
    • Koden i det finally blocket körs.
    • Om det finally blocket kastar ett undantag förökas detta undantag.
    • Annars fortsätter det ursprungliga undantaget att spridas.

Koden inom finally block kommer alltid att köras. (De enda undantagen är om System.exit(int) kallas, eller om JVM-panik.) Således är ett finally block den korrekta platskoden som alltid behöver köras; t.ex. stänga filer och andra resurser eller släppa lås.

try-catch-slutligen

Vårt andra exempel visar hur catch och finally kan användas tillsammans. Det illustrerar också att rensningen av resurser inte är enkel.

// 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
        }
    }
}

Den kompletta uppsättningen (hypotetiska) beteenden med try...catch...finally i detta exempel är för komplicerat för att beskriva här. Den enkla versionen är att koden i det finally blocket alltid kommer att köras.

Att titta på detta ur resurshanteringsperspektivet:

  • Vi förklarar "resursen" (dvs. reader ) innan try så att det kommer att vara i omfattning för det finally blocket.
  • Genom att sätta den new FileReader(...) kan catch hantera alla IOError undantag som kastas när filen öppnas.
  • Vi behöver en reader.close() i det finally blocket eftersom det finns några undantagsvägar som vi inte kan fånga varken i try eller i catch .
  • Men eftersom ett undantag kan ha kastats innan reader initialiserades, behöver vi också ett uttryckligt null .
  • Slutligen kan reader.close() hypotetiskt) kasta ett undantag. Vi bryr oss inte om det, men om vi inte tar undantaget vid källan, skulle vi behöva ta itu med det längre upp i samtalstacken.
Java SE 7

Java 7 och senare tillhandahåller en alternativ syntax med försök med resurser som väsentligt förenklar resursrensningen.

"Kast" -klausulen i en metoddeklaration

Javas kontrollerade undantagsmekanism kräver att programmeraren förklarar att vissa metoder kan kasta specifika kontrollerade undantag. Detta görs med hjälp av throws klausulen. Till exempel:

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

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

throws OddNumberException förklarar att ett samtal till checkEven kan kasta ett undantag som är av typen OddNumberException .

A throws klausul kan förklara en förteckning över sådana, och kan innehålla okontrollerade undantag samt kontrolleras undantag.

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();
    }
}

Vad är poängen med att förklara underkända undantag som kastade?

Den throws klausul i en metoddeklaration tjänar två syften:

  1. Den berättar kompilatorn vilka undantag som kastas så att kompilatorn kan rapportera om inte fångade (kontrollerade) undantag som fel.

  2. Den berättar för en programmerare som skriver kod som kallar metoden vilka undantag man kan förvänta sig. För detta ändamål gör det ofta kännedom att ta med okontrollerade undantag i en throws .

Obs: att throws också används av javadoc-verktyget vid generering av API-dokumentation och av en typisk IDE: s "hover text" -metodtips.

Kaster och åsidosätter metoden

Den throws klausul utgör en del av en metod signatur för att metoden övergripande. En åsidosättningsmetod kan deklareras med samma uppsättning kontrollerade undantag som kastas av den åsidosatta metoden eller med en delmängd. Men åsidosättningsmetoden kan inte lägga till extra kontrollerade undantag. Till exempel:

@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

Anledningen till denna regel är att om en åsidosättningsmetod kan kasta ett kontrollerat undantag som den åsidosatta metoden inte kunde kasta, skulle det bryta typersättningsbarhet.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow