Java Language
Undantag och undantagshantering
Sök…
Introduktion
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 eftertry...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
.
- Variabeln
- Om det inte är det, fortsätter det ursprungliga undantaget att spridas.
- Undantagsobjektet testas för att se om det är ett exempel på
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
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 klassenIllegalArgumentException
, 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
somthrows IllegalArgumentException
. Detta var inte strikt nödvändigt eftersomIllegalArgumentException
ä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:
- UnsupportedOperationException - en viss operation stöds inte
- IllegalArgumentException - ett ogiltigt parametervärde överfördes till en metod
- IllegalStateException - ditt API har internt nått ett villkor som aldrig borde hända, eller som uppstår till följd av att du använder ditt API på ogiltigt sätt
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
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
ochServerSocket
och deras underklasser -
Channel
och dess underklasser, och - JDBC-gränssnitten
Connection
,Statement
ochResultSet
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 blockerarfinally
. - 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:
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();
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
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:
- Skillnaden mellan kontrollerade och okontrollerade undantag beskrivs nedan.
-
Throwable
,Exception
ochRuntimeException
bör behandlas somabstract
; se Pitfall - Throwing Throwable, Exception, Error or RuntimeException . -
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. - Det vore oklokt att förklara anpassade undertyper av
Throwable
. Java-verktyg och bibliotek kan anta attError
ochException
är de enda direkta undertyperna avThrowable
, och kanThrowable
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 denthrows
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:
- Börja köra koden i
try
. - 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
). - Om undantaget fångas tilldelas det variabeln
e
ochcatch
körs. - Om antingen
try
ellercatch
är avslutat (dvs inga oupptagna undantag inträffar under kodutförandet) fortsätter du att köra koden undertry-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
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:
Informationen som finns tillgänglig i
StackTraceElement
är begränsad. Det finns ingen mer information tillgänglig än som visas avprintStackTrace
. (Värdena på de lokala variablerna i ramen är inte tillgängliga.)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
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 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()
på 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
.
- Koden i det
- 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 i det
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
) innantry
så att det kommer att vara i omfattning för detfinally
blocket. - Genom att sätta den
new FileReader(...)
kancatch
hantera allaIOError
undantag som kastas när filen öppnas. - Vi behöver en
reader.close()
i detfinally
blocket eftersom det finns några undantagsvägar som vi inte kan fånga varken itry
eller icatch
. - Men eftersom ett undantag kan ha kastats innan
reader
initialiserades, behöver vi också ett uttryckligtnull
. - 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 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:
Den berättar kompilatorn vilka undantag som kastas så att kompilatorn kan rapportera om inte fångade (kontrollerade) undantag som fel.
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.