成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Java中的異常處理:高級(jí)特性和類型

譯文
開發(fā) 前端
Java異常處理的高級(jí)特性包括堆棧跟蹤、異常鏈、Try-with-resources、多重捕獲、最終重新拋出異常,以及堆棧遍歷。

譯者 | 李睿

審校 | 重樓

Java平臺(tái)包含了多種語(yǔ)言特性和庫(kù)類型,用于處理與預(yù)期程序行為不同的異常。本文將介紹Java中異常處理的高級(jí)特性,其中包括堆棧跟蹤(stack traces)、異常鏈(exception chaining)、try-with-resources重捕獲multi-catch)、最終重新拋出異常final re-throw,以及堆棧遍歷(stack walking)。

Java教程的學(xué)習(xí)內(nèi)容

以下將介紹在Java程序中處理異常的高級(jí)特性:

  • 堆棧跟蹤和堆棧跟蹤元素
  • 原因(Causes)和異常鏈exception chaining)
  • Try-with-resources
  • 多重捕獲(multi-catch)
  • 最終重新拋出異常final re-throw
  • StackWalking和StackWalking API

使用堆棧跟蹤進(jìn)行異常處理

每個(gè)JVM線程(執(zhí)行路徑)都與創(chuàng)建線程時(shí)創(chuàng)建的堆棧相關(guān)聯(lián)。這個(gè)數(shù)據(jù)結(jié)構(gòu)被劃分為幀(frame),這些幀是與方法調(diào)用相關(guān)聯(lián)的數(shù)據(jù)結(jié)構(gòu)。因此,每個(gè)線程的堆棧通常被稱為方法調(diào)用堆棧。

每當(dāng)調(diào)用一個(gè)方法時(shí),就會(huì)創(chuàng)建一個(gè)新的幀。每幀存儲(chǔ)局部變量、參數(shù)變量(保存?zhèn)鬟f給方法的參數(shù))、返回到調(diào)用方法的信息、存儲(chǔ)返回值的空間、在調(diào)度異常時(shí)有用的信息等等。

堆棧跟蹤是在線程執(zhí)行過(guò)程中某個(gè)時(shí)間點(diǎn)的活動(dòng)堆棧幀的報(bào)告。Java的Throwable類(在Java.lang包中)提供了打印堆棧跟蹤、填充堆棧跟蹤和訪問(wèn)堆棧跟蹤元素的方法。

下載本教程中示例的源代碼

打印堆棧跟蹤

當(dāng)throw語(yǔ)句拋出一個(gè)throwable時(shí),它首先會(huì)在當(dāng)前執(zhí)行的方法中查找一個(gè)合適的catch塊。如果沒(méi)有找到,它會(huì)回溯方法調(diào)用棧,尋找能夠處理該異常的最近的catch塊。如果仍然沒(méi)有找到,JVM將終止執(zhí)行,并顯示一條合適的消息。可以在清單1中看到這一行為。

清單1.PrintStackTraceDemo.java (version 1)

import java.io.IOException;
public class PrintStackTraceDemo
{
 public static void main(String[] args) throws IOException
 {
 throw new IOException();
 }
}

清單1的示例創(chuàng)建了一個(gè)java.io.IOException對(duì)象,并將該對(duì)象拋出main()方法。因?yàn)閙ain()不處理這個(gè)throwable,而且main()是頂級(jí)方法,因此JVM會(huì)終止執(zhí)行,并顯示一條合適的消息。對(duì)于這個(gè)應(yīng)用程序,將看到以下消息:

Exception in thread "main" java.io.IOException
 at PrintStackTraceDemo.main(PrintStackTraceDemo.java:7)

JVM通過(guò)調(diào)用Throwable的void printStackTrace()方法輸出這條消息,該方法在標(biāo)準(zhǔn)錯(cuò)誤流上打印調(diào)用throwable對(duì)象的堆棧跟蹤。輸出的第一行顯示了調(diào)用throwable對(duì)象的toString()方法的結(jié)果。下一行顯示了fillInStackTrace()之前記錄的數(shù)據(jù)(稍后討論)。

其他的打印堆棧跟蹤方法

throwable的重載void printStackTrace(PrintStream ps)和void printStackTrace(printwwriter pw)方法將堆棧跟蹤輸出到指定的流或?qū)懭肫鳌?/span>

堆棧跟蹤顯示了創(chuàng)建throwable的源文件和行號(hào)。在本例中,它是在PrintStackTrace.java源文件的第7行創(chuàng)建的。

可以直接調(diào)用printStackTrace(),通常是調(diào)用catch塊。例如,考慮PrintStackTraceDemo應(yīng)用程序的第二個(gè)版本。

清單2. PrintStackTraceDemo.java (version 2)

import java.io.IOException;
public class PrintStackTraceDemo
{
 public static void main(String[] args) throws IOException
 {
 try
 {
 a();
 }
 catch (IOException ioe)
 {
 ioe.printStackTrace();
 }
 }
 static void a() throws IOException
 {
 b();
 }
 static void b() throws IOException
 {
 throw new IOException();
 }
}

清單2展示了一個(gè)main()方法,它調(diào)用方法a(),而方法a()又調(diào)用方法b()。方法b()向JVM拋出一個(gè)IOException對(duì)象,JVM將展開方法調(diào)用堆棧,直到找到main()的catch塊,該塊可以處理異常。異常是通過(guò)對(duì)throwable調(diào)用printStackTrace()來(lái)處理的。這種方法產(chǎn)生以下輸出:

java.io.IOException
 at PrintStackTraceDemo.b(PrintStackTraceDemo.java:24)
 at PrintStackTraceDemo.a(PrintStackTraceDemo.java:19)
 at PrintStackTraceDemo.main(PrintStackTraceDemo.java:9)

printStackTrace()不會(huì)輸出線程的名稱。與其相反,它首先會(huì)在第一行調(diào)用 Throwable 對(duì)象的 toString() 方法,以返回異常的完全限定類名(如 java.io.IOException),這是輸出的第一部分然后輸出方法調(diào)用層次結(jié)構(gòu):最近調(diào)用的方法(b())位于頂部,main()位于底部。

堆棧跟蹤標(biāo)識(shí)的是哪一行?

堆棧跟蹤標(biāo)識(shí)了創(chuàng)建throwable的行,但它不會(huì)標(biāo)識(shí)拋出throwable的行(通過(guò)throw),除非拋出和創(chuàng)建是在同一行代碼中完成的。

填充堆棧跟蹤

throwable聲明了一個(gè)Throwable fillInStackTrace()方法來(lái)填充執(zhí)行堆棧跟蹤。在調(diào)用Throwable對(duì)象中,它記錄有關(guān)當(dāng)前線程堆棧幀的當(dāng)前狀態(tài)的信息。考慮清單3。

清單3. FillInStackTraceDemo.java (version 1)

import java.io.IOException;
public class FillInStackTraceDemo
{
 public static void main(String[] args) throws IOException
 {
 try
 {
 a();
 }
 catch (IOException ioe)
 {
 ioe.printStackTrace();
 System.out.println();
 throw (IOException) ioe.fillInStackTrace();
 }
 }
 static void a() throws IOException
 {
 b();
 }
 static void b() throws IOException
 {
 throw new IOException();
 }
}

清單3和清單2的主要區(qū)別在于catch塊的throw (IOException) ioe.fillInStackTrace();聲明。該語(yǔ)句替換ioe的堆棧跟蹤,然后重新拋出throwable。應(yīng)該看到這樣的輸出:

java.io.IOException
 at FillInStackTraceDemo.b(FillInStackTraceDemo.java:26)
 at FillInStackTraceDemo.a(FillInStackTraceDemo.java:21)
 at FillInStackTraceDemo.main(FillInStackTraceDemo.java:9)
Exception in thread "main" java.io.IOException
 at FillInStackTraceDemo.main(FillInStackTraceDemo.java:15)

第二個(gè)堆棧跟蹤顯示ioe.fillInStackTrace()的位置,而不是重復(fù)標(biāo)識(shí)IOException對(duì)象創(chuàng)建位置的初始堆棧跟蹤。

Throwable構(gòu)造函數(shù)和fillInStackTrace()

throwable的每個(gè)構(gòu)造函數(shù)都調(diào)用fillInStackTrace()。然而,當(dāng)將false傳遞給writableStackTrace時(shí),下面的構(gòu)造函數(shù)(在JDK 7中引入)不會(huì)調(diào)用這個(gè)方法:

Throwable(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace)

fillInStackTrace()調(diào)用一個(gè)原生方法,該方法沿著當(dāng)前線程的方法調(diào)用堆棧遍歷以構(gòu)建堆棧跟蹤。這種遍歷代價(jià)高昂,如果過(guò)于頻繁,可能會(huì)影響性能。

如果遇到性能非常關(guān)鍵的情況(可能涉及嵌入式設(shè)備),可以通過(guò)重寫fillInStackTrace()來(lái)阻止堆棧跟蹤的構(gòu)建。請(qǐng)查看清單4。

清單4. FillInStackTraceDemo.java (version 2)

{
 public static void main(String[] args) throws NoStackTraceException
 {
 try
 {
 a();
 }
 catch (NoStackTraceException nste)
 {
 nste.printStackTrace();
 }
 }
 static void a() throws NoStackTraceException
 {
 b();
 }
 static void b() throws NoStackTraceException
 {
 throw new NoStackTraceException();
 }
}
class NoStackTraceException extends Exception
{
 @Override
 public synchronized Throwable fillInStackTrace()
 {
 return this;
 }
}

清單4引入了NoStackTraceException。這個(gè)自定義檢查的異常類重寫fillInStackTrace()以返回This——對(duì)調(diào)用Throwable的引用。該程序生成以下輸出:

NoStackTraceException

注釋掉覆蓋的fillInStackTrace()方法,會(huì)看到以下輸出:

NoStackTraceException
 at FillInStackTraceDemo.b(FillInStackTraceDemo.java:22)
 at FillInStackTraceDemo.a(FillInStackTraceDemo.java:17)
 at FillInStackTraceDemo.main(FillInStackTraceDemo.java:7)

訪問(wèn)堆棧跟蹤的元素

有時(shí),需要訪問(wèn)堆棧跟蹤的元素,以便提取日志記錄、識(shí)別資源泄漏源和其他目的所需的詳細(xì)信息。printStackTrace()和fillInStackTrace()方法不支持這個(gè)任務(wù),但是java.lang.StackTraceElement和它的方法就是為這個(gè)任務(wù)設(shè)計(jì)的。

stacktraceelement類描述了在堆棧跟蹤中表示堆棧幀的元素。它的方法可用于返回類的完全限定名,該類包含這個(gè)堆棧跟蹤元素所表示的執(zhí)行點(diǎn)以及其他有用信息。以下是該類的主要方法:

  • String getClassName()返回包含這個(gè)堆棧跟蹤元素所表示的執(zhí)行點(diǎn)的類的完全限定名。
  • String getFileName()返回包含這個(gè)堆棧跟蹤元素所表示的執(zhí)行點(diǎn)的源文件的名稱。
  • int getLineNumber()返回包含這個(gè)堆棧跟蹤元素所表示的執(zhí)行點(diǎn)的源行的行號(hào)。
  • String getMethodName()返回包含這個(gè)堆棧跟蹤元素所表示的執(zhí)行點(diǎn)的方法的名稱。
  • 當(dāng)包含這個(gè)堆棧跟蹤元素所表示的執(zhí)行點(diǎn)的方法是原生方法時(shí),boolean isNativeMethod() 返回true。

另一個(gè)重要方法是java.lang.Thread和Throwable類上的StackTraceElement[]getStackTrace()。這個(gè)方法分別返回一個(gè)堆棧跟蹤元素?cái)?shù)組,表示調(diào)用線程的堆棧轉(zhuǎn)儲(chǔ),并提供對(duì)printStackTrace()打印的堆棧跟蹤信息的程序化訪問(wèn)。

清單5展示了StackTraceElement和getStackTrace()。

清單5. StackTraceElementDemo.java (version 1)

import java.io.IOException;
public class StackTraceElementDemo
{
 public static void main(String[] args) throws IOException
 {
 try
 {
 a();
 }
 catch (IOException ioe)
 {
 StackTraceElement[] stackTrace = ioe.getStackTrace();
 for (int i = 0; i < stackTrace.length; i++)
 {
 System.err.println("Exception thrown from " + 
 stackTrace[i].getMethodName() + " in class " + 
 stackTrace[i].getClassName() + " on line " + 
 stackTrace[i].getLineNumber() + " of file " + 
 stackTrace[i].getFileName());
 System.err.println();
 }
 }
 }
 static void a() throws IOException
 {
 b();
 }
 static void b() throws IOException
 {
 throw new IOException();
 }
}

當(dāng)運(yùn)行這個(gè)應(yīng)用程序時(shí),會(huì)看到以下輸出:

Exception thrown from b in class StackTraceElementDemo on line 33 of file StackTraceElementDemo.java
Exception thrown from a in class StackTraceElementDemo on line 28 of file StackTraceElementDemo.java
Exception thrown from main in class StackTraceElementDemo on line 9 of file StackTraceElementDemo.java

最后,在Throwable類上有setStackTrace()方法。這種方法是為遠(yuǎn)程過(guò)程調(diào)用(RPC)框架和其他高級(jí)系統(tǒng)設(shè)計(jì)的,允許客戶端覆蓋在構(gòu)造Throwable時(shí)由fillInStackTrace()生成的默認(rèn)堆棧跟蹤。

之前展示了如何重寫fillInStackTrace()以防止構(gòu)建堆棧跟蹤。然而,可以使用StackTraceElement和setStackTrace()來(lái)安裝新的堆棧跟蹤。可以創(chuàng)建一個(gè)通過(guò)以下構(gòu)造函數(shù)初始化的StackTraceElement對(duì)象數(shù)組,并將該數(shù)組傳遞給setStackTrace():

StackTraceElement(String declaringClass, String methodName, String fileName, int lineNumber)

清單6演示了StackTraceElement和setStackTrace()。

清單6 . StackTraceElementDemo.java (version 2)

public class StackTraceElementDemo
{
 public static void main(String[] args) throws NoStackTraceException
 {
 try
 {
 a();
 }
 catch (NoStackTraceException nste)
 {
 nste.printStackTrace();
 }
 }
 static void a() throws NoStackTraceException
 {
 b();
 }
 static void b() throws NoStackTraceException
 {
 throw new NoStackTraceException();
 }
}
class NoStackTraceException extends Exception
{
 @Override
 public synchronized Throwable fillInStackTrace()
 {
 setStackTrace(new StackTraceElement[]
 {
 new StackTraceElement("*StackTraceElementDemo*", 
 "b()",
 "StackTraceElementDemo.java",
 22),
 new StackTraceElement("*StackTraceElementDemo*", 
 "a()",
 "StackTraceElementDemo.java",
 17),
 new StackTraceElement("*StackTraceElementDemo*", 
 "main()",
 "StackTraceElementDemo.java",
 7)
 });
 return this;
 }
}

采用星號(hào)包圍了StackTraceElementDemo類名,以證明這個(gè)堆棧跟蹤是輸出的跟蹤。運(yùn)行應(yīng)用程序,將觀察到以下堆棧跟蹤:

NoStackTraceException
 at *StackTraceElementDemo*.b()(StackTraceElementDemo.java:22)
 at *StackTraceElementDemo*.a()(StackTraceElementDemo.java:17)
 at *StackTraceElementDemo*.main()(StackTraceElementDemo.java:7)

原因和異常鏈

在異常處理中,一個(gè)catch塊經(jīng)常會(huì)通過(guò)拋出另一個(gè)異常來(lái)響應(yīng)捕獲到的異常。第一個(gè)異常被認(rèn)為是導(dǎo)致第二個(gè)異常發(fā)生的原因,這兩個(gè)異常被隱式地鏈接在一起。

例如,調(diào)用庫(kù)方法的catch塊時(shí)可能會(huì)出現(xiàn)內(nèi)部異常,而該內(nèi)部異常對(duì)標(biāo)準(zhǔn)庫(kù)方法的調(diào)用者不應(yīng)該是可見(jiàn)的。因?yàn)樾枰ㄖ{(diào)用者出現(xiàn)了錯(cuò)誤,所以catch塊會(huì)創(chuàng)建一個(gè)符合庫(kù)方法合約接口的外部異常,并且調(diào)用者可以處理這個(gè)異常。

由于內(nèi)部異常可能對(duì)于診斷問(wèn)題非常有幫助,catch塊應(yīng)該將內(nèi)部異常包裝在外部異常中。被包裝的異常被稱為原因(cause),因?yàn)樗拇嬖趯?dǎo)致拋出外部異常。此外,包裝的內(nèi)部異常(原因)被顯式地鏈接到外部異常。

對(duì)原因和異常鏈的支持是通過(guò)兩個(gè)Throwable構(gòu)造函數(shù)(以及對(duì)應(yīng)的exception、RuntimeException和Error)和兩種方法提供的:

  • Throwable(String message, Throwable cause)
  • Throwable(Throwable cause)
  • Throwable getCause()
  • Throwable initCause(Throwable cause)

Throwable構(gòu)造函數(shù)(及其對(duì)應(yīng)的子類)允許在構(gòu)造Throwable時(shí)包裝原因。如果處理的教學(xué)法遺留代碼的自定義異常類不支持任何一個(gè)構(gòu)造函數(shù),可以通過(guò)在Throwable上調(diào)用initCause()來(lái)包裝原因。需要注意的是,initCause()只能被調(diào)用一次。無(wú)論哪種方式,都可以通過(guò)調(diào)用getCause()返回原因。當(dāng)沒(méi)有原因時(shí),這個(gè)方法返回null。

清單7 展示了一個(gè)名為 CauseDemo 的應(yīng)用程序,該應(yīng)用程序演示了異常原因(以及異常鏈)的概念。

清單7. CauseDemo.java

public class CauseDemo
{
 public static void main(String[] args)
 {
 try
 {
 Library.externalOp();
 } 
 catch (ExternalException ee)
 {
 ee.printStackTrace();
 }
 }
}
class Library
{
 static void externalOp() throws ExternalException
 {
 try
 {
 throw new InternalException();
 } 
 catch (InternalException ie)
 {
 throw (ExternalException) new ExternalException().initCause(ie);
 }
 }
 private static class InternalException extends Exception
 {
 }
}
class ExternalException extends Exception
{
}

清單7顯示了CauseDemo、Library和ExternalException類。CauseDemo的main()方法調(diào)用Library的externalOp()方法并catch其拋出的ExternalException對(duì)象。catch塊調(diào)用printStackTrace()來(lái)輸出外部異常及其原因。

庫(kù)的externalOp()方法故意拋出一個(gè)InternalException對(duì)象,其catch塊將該對(duì)象映射到一個(gè)ExternalException對(duì)象。因?yàn)镋xternalException不支持可以接受cause參數(shù)的構(gòu)造函數(shù),所以使用initCause()來(lái)包裝InternalException對(duì)象。

運(yùn)行這個(gè)應(yīng)用程序,就會(huì)看到下面的堆棧跟蹤:

ExternalException
 at Library.externalOp(CauseDemo.java:26)
 at CauseDemo.main(CauseDemo.java:7)
Caused by: Library$InternalException
 at Library.externalOp(CauseDemo.java:22)
 ... 1 more

第一個(gè)堆棧跟蹤顯示,外部異常起源于Library的externalOp()方法(CauseDemo.java中的第26行),并在第7行對(duì)該方法的調(diào)用中拋出。第二個(gè)堆棧跟蹤顯示了內(nèi)部異常原因源自Library的externalOp()方法(CauseDemo.java中的第22行)。... 1 more行表示第一個(gè)堆棧跟蹤的最后一行。如果可以刪除這一行,將看到以下輸出:

ExternalException
 at Library.externalOp(CauseDemo.java:26)
 at CauseDemo.main(CauseDemo.java:7)
Caused by: Library$InternalException
 at Library.externalOp(CauseDemo.java:22)
 at CauseDemo.main(CauseDemo.java:7)

可以通過(guò)改變ee.printStackTrace(); to來(lái)證明第二個(gè)跟蹤的最后一行是第一個(gè)堆棧跟蹤的最后一行的副本。

ee.getCause().printStackTrace();.

關(guān)于...more”以及探究原因鏈的更多信息

通常,...n more這樣的表述意味著原因的堆棧跟蹤的最后n行與前一個(gè)堆棧跟蹤的最后n行是重復(fù)的。

這個(gè)例子只揭示了一個(gè)原因。然而,從非簡(jiǎn)單的現(xiàn)實(shí)世界應(yīng)用程序中拋出的異常可能包含由多個(gè)原因構(gòu)成的復(fù)雜鏈。可以通過(guò)使用如下所示的循環(huán)來(lái)訪問(wèn)這些原因:

catch (Exception exc)
{
 Throwable t = exc.getCause();
 while (t != null)
 {
 System.out.println(t);
 t = t.getCause();
 }
}

Try-with-resources

Java應(yīng)用程序經(jīng)常訪問(wèn)文件、數(shù)據(jù)庫(kù)連接、套接字和其他依賴于相關(guān)系統(tǒng)資源的資源(例如文件句柄)。系統(tǒng)資源的稀缺性意味著它們最終必須被釋放,即使在發(fā)生異常時(shí)也是如此。當(dāng)系統(tǒng)資源沒(méi)有被釋放,應(yīng)用程序在嘗試獲取其他資源時(shí)最終會(huì)失敗,因?yàn)闆](méi)有更多相關(guān)的系統(tǒng)資源可用。

在對(duì)異常處理基礎(chǔ)的介紹中,提到資源(實(shí)際上是它們所依賴的系統(tǒng)資源)在finally塊中釋放。這可能會(huì)導(dǎo)致冗長(zhǎng)的樣板代碼,例如下面顯示的文件關(guān)閉代碼:

finally
{
 if (fis != null)
 try
 {
 fis.close();
 }
 catch (IOException ioe)
 {
 // ignore exception
 }
 if (fos != null)
 try
 {
 fos.close();
 }
 catch (IOException ioe)
 {
 // ignore exception
 }
}

這個(gè)樣板代碼不僅增加了類文件的容量,而且編寫它的單調(diào)乏味可能會(huì)導(dǎo)致錯(cuò)誤,甚至可能無(wú)法關(guān)閉文件。JDK 7引入了try-with-resource”來(lái)克服這個(gè)問(wèn)題。

try-with-resource的基本原理

當(dāng)執(zhí)行離開打開和使用資源的范圍時(shí),try-with-resources構(gòu)造會(huì)自動(dòng)關(guān)閉打開的資源,無(wú)論是否從該范圍拋出異常。這個(gè)構(gòu)造的語(yǔ)法如下:

try (resource acquisitions)
{
 // resource usage
}

try關(guān)鍵字由分號(hào)分隔的資源獲取語(yǔ)句列表參數(shù)化,其中每條語(yǔ)句獲取一個(gè)資源。每個(gè)獲取的資源都可用于try塊的主體,并在執(zhí)行離開該主體時(shí)自動(dòng)關(guān)閉。與常規(guī)的try語(yǔ)句不同,try-with-resource不需要catch塊和/或finally塊來(lái)跟隨try(),盡管它們可以指定。

考慮以下面向文件的示例:

try (FileInputStream fis = new FileInputStream("abc.txt"))
{
 // Do something with fis and the underlying file resource.
}

在這個(gè)例子中,獲取了底層文件資源(abc.txt)的輸入流。try塊使用這個(gè)資源執(zhí)行某些操作,流(以及文件)在退出try塊時(shí)關(guān)閉。

將“var”與“try-with-resource”一起使用

JDK 10引入了對(duì)var的支持,var是一種具有特殊含義的標(biāo)識(shí)符(即不是關(guān)鍵字)。可以使用var和try with資源來(lái)減少樣板。例如,可以將前面的示例簡(jiǎn)化為以下內(nèi)容:

try (var fis = new FileInputStream("abc.txt"))
{
 // Do something with fis and the underlying file resource.
}

try-with-resource場(chǎng)景中復(fù)制文件

本文作者從文件復(fù)制應(yīng)用程序中摘錄了copy()方法。這種方法的finally塊包含前面介紹的文件關(guān)閉樣板。清單8通過(guò)使用try-with-resources處理清理,從而改進(jìn)這種方法。

清單8. Copy.java

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Copy
{
 public static void main(String[] args)
 {
 if (args.length != 2)
 {
 System.err.println("usage: java Copy srcfile dstfile");
 return;
 }
 try
 {
 copy(args[0], args[1]);
 }
 catch (IOException ioe)
 {
 System.err.println("I/O error: " + ioe.getMessage());
 }
 }
 static void copy(String srcFile, String dstFile) throws IOException
 {
 try (FileInputStream fis = new FileInputStream(srcFile);
 FileOutputStream fos = new FileOutputStream(dstFile))
 {
 int c;
 while ((c = fis.read()) != -1)
 fos.write(c);
 }
 }
}

copy()使用try-with-resources來(lái)管理源文件和目標(biāo)文件資源。下面的圓括號(hào)代碼嘗試創(chuàng)建這些文件的文件輸入和輸出流。假設(shè)成功,它的主體將執(zhí)行,將源文件復(fù)制到目標(biāo)文件。

無(wú)論是否拋出異常,try-with-resources都能確保在執(zhí)行離開try塊時(shí)關(guān)閉這兩個(gè)文件。因?yàn)椴恍枰懊骘@示的樣板文件關(guān)閉代碼,所以清單8的copy()方法要簡(jiǎn)單得多,也更容易閱讀。

設(shè)計(jì)資源類以支持try-with-resources

try-with-resources構(gòu)造要求資源類實(shí)現(xiàn)java.lang.Closeable接口或JDK 7引入的java.lang.AutoCloseable超級(jí)接口。Java7之前的類(如Java.io.FileInputStream)實(shí)現(xiàn)了Closeable接口,它提供了一個(gè)拋出IOException或子類的void close()方法。

從Java 7開始,類可以實(shí)現(xiàn)AutoCloseable,其單個(gè)void close()方法可以拋出Java.lang.Exception或子類。throws子句已經(jīng)擴(kuò)展,以適應(yīng)可能需要添加close()方法的情況,這些方法可以在IOException層次結(jié)構(gòu)之外拋出異常;例如java.sql.SQLException

清單9顯示了一個(gè)CustomARM應(yīng)用程序,它展示了如何配置自定義資源類,以便可以在try-with-resources場(chǎng)景中使用它。

清單9. CustomARM.java

public class CustomARM
{
 public static void main(String[] args)
 {
 try (USBPort usbp = new USBPort())
 {
 System.out.println(usbp.getID());
 }
 catch (USBException usbe)
 {
 System.err.println(usbe.getMessage());
 }
 }
}

class USBPort implements AutoCloseable
{
 USBPort() throws USBException
 {
 if (Math.random() < 0.5)
 throw new USBException("unable to open port");
 System.out.println("port open");
 }

 @Override
 public void close()
 {
 System.out.println("port close");
 }

 String getID()
 {
 return "some ID";
 }
}

class USBException extends Exception
{
 USBException(String msg)
 {
 super(msg);
 }
}

清單9模擬了一個(gè)USB端口,可以打開和關(guān)閉該端口,并返回端口的 ID。在構(gòu)造函數(shù)中,使用了 Math.random() 來(lái)模擬可能拋出異常的情況,以便可以觀察到 try-with-resources 語(yǔ)句在異常被拋出或未拋出時(shí)的行為。

編譯這個(gè)清單并運(yùn)行應(yīng)用程序。如果端口打開,將看到以下輸出:

port open
some ID
port close

如果端口關(guān)閉,將看到以下輸出:

unable to open port

在try-with-resources中抑制異常

如果你有一定的編程經(jīng)驗(yàn),可能會(huì)注意到try-with-resources結(jié)構(gòu)中存在一個(gè)潛在的問(wèn)題:假設(shè)try塊拋出了一個(gè)異常。這個(gè)結(jié)構(gòu)通過(guò)調(diào)用資源對(duì)象的close()方法來(lái)關(guān)閉資源以出響應(yīng)。然而,close()方法本身也可能拋出異常。

當(dāng)close()方法拋出異常時(shí)(例如,F(xiàn)ileInputStream的void close()方法可以拋出IOException),這個(gè)異常會(huì)掩蓋或隱藏原始異常。這看起來(lái)像是原始的異常丟失了。

實(shí)際上,情況并非如此:try-with-resources會(huì)抑制close()拋出的異常。它還通過(guò)調(diào)用java.lang.Throwable的void addSuppressed(Throwable exception)方法,將異常添加到原始異常的抑制異常數(shù)組中。

清單10展示了一個(gè)SupExDemo應(yīng)用程序,它演示了如何在try-with-resources的場(chǎng)景中抑制異常。

清單10. SupExDemo.java

import java.io.Closeable;
import java.io.IOException;
public class SupExDemo implements Closeable
{
 @Override
 public void close() throws IOException
 {
 System.out.println("close() invoked");
 throw new IOException("I/O error in close()");
 }
 public void doWork() throws IOException
 {
 System.out.println("doWork() invoked");
 throw new IOException("I/O error in work()");
 }
 public static void main(String[] args) throws IOException
 {
 try (SupExDemo supexDemo = new SupExDemo())
 {
 supexDemo.doWork();
 }
 catch (IOException ioe)
 {
 ioe.printStackTrace();
 System.out.println();
 System.out.println(ioe.getSuppressed()[0]);
 }
 }
}

清單10的doWork()方法拋出IOException來(lái)模擬某種I/O錯(cuò)誤。close()方法還會(huì)拋出IOException,但這個(gè)異常被抑制,這樣它就不會(huì)掩蓋doWork()的異常。

catch塊通過(guò)調(diào)用Throwable的Throwable[]getSuppressed()方法來(lái)訪問(wèn)被抑制的異常(從close()拋出的異常),該方法返回一個(gè)包含被抑制異常的數(shù)組。由于在這個(gè)例子中只有一個(gè)異常被抑制,所以只訪問(wèn)了數(shù)組的第一個(gè)元素。

編譯清單10并運(yùn)行應(yīng)用程序。應(yīng)該觀察以下輸出:

doWork() invoked
close() invoked
java.io.IOException: I/O error in work()
 at SupExDemo.doWork(SupExDemo.java:16)
 at SupExDemo.main(SupExDemo.java:23)
 Suppressed: java.io.IOException: I/O error in close()
 at SupExDemo.close(SupExDemo.java:10)
 at SupExDemo.main(SupExDemo.java:24)
java.io.IOException: I/O error in close()

多重捕獲塊(multi-catch)

從JDK 7開始,可以在單個(gè)catch塊中捕獲多種類型的異常。這種多重捕獲特性的目的是減少代碼重復(fù),并減少捕獲過(guò)于寬泛的異常(例如,catch (Exception e))的誘惑。

假設(shè)開發(fā)了一個(gè)應(yīng)用程序,可以靈活地將數(shù)據(jù)復(fù)制到數(shù)據(jù)庫(kù)或文件中。清單11展示了一個(gè)CopyToDatabaseOrFile類,該類模擬了這種情況,并演示了catch塊代碼重復(fù)的問(wèn)題。

清單11.CopyToDatabaseOrFile.java

import java.io.IOException;
import java.sql.SQLException;
public class CopyToDatabaseOrFile
{
 public static void main(String[] args)
 {
 try
 {
 copy();
 }
 catch (IOException ioe)
 {
 System.out.println(ioe.getMessage());
 // additional handler code
 }
 catch (SQLException sqle)
 {
 System.out.println(sqle.getMessage());
 // additional handler code that's identical to the previous handler's
 // code
 }
 }
 static void copy() throws IOException, SQLException
 {
 if (Math.random() < 0.5)
 throw new IOException("cannot copy to file");
 else
 throw new SQLException("cannot copy to database");
 }
}

JDK 7通過(guò)允許在catch塊中指定多個(gè)異常類型來(lái)克服代碼重復(fù)問(wèn)題,其中每個(gè)連續(xù)的類型都通過(guò)在這些類型之間放置豎線(|)與其前一個(gè)類型分開:

try
{
 copy();
}
catch (IOException | SQLException iosqle)
{
 System.out.println(iosqle.getMessage());
}

現(xiàn)在,當(dāng)copy()拋出異常時(shí),異常將被捕獲并由catch塊處理。

當(dāng)在catch塊的頭文件中列出多個(gè)異常類型時(shí),該參數(shù)被隱式地視為final。因此,不能更改參數(shù)的值。例如,不能更改存儲(chǔ)在前一個(gè)代碼片段的iosqle參數(shù)中的引用。

縮減字節(jié)碼

編譯處理多種異常類型的catch塊所產(chǎn)生的字節(jié)碼將比編譯每個(gè)只處理列出的一種異常類型的幾個(gè)catch塊要小。處理多種異常類型的catch塊在編譯期間不會(huì)產(chǎn)生重復(fù)的字節(jié)碼。換句話說(shuō),字節(jié)碼不包含復(fù)制的異常處理程序。

最終重新拋出異常

從JDK 7開始,Java編譯器能夠比之前的Java版本更精確地分析被重拋的異常。這個(gè)特性僅在沒(méi)有對(duì)被重拋的異常的catch塊參數(shù)進(jìn)行賦值時(shí)有效,該參數(shù)被認(rèn)為是有效的final。當(dāng)前面的try塊拋出一個(gè)屬于參數(shù)類型的超類型/子類型的異常時(shí),編譯器會(huì)拋出捕獲的異常的實(shí)際類型,而不是拋出參數(shù)的類型(就像以前的Java版本那樣)。

這個(gè)最終重拋異常特性的目的是為了方便在代碼塊周圍添加try-catch語(yǔ)句來(lái)攔截、處理并重拋異常,同時(shí)不影響從代碼中靜態(tài)確定的異常集。此外,這個(gè)特性允許在異常被拋出的地方附近提供一個(gè)通用的異常處理器來(lái)部分處理異常,并在其他地方提供更精確的處理程序來(lái)處理重拋的異常。考慮清單12。

清單12.MonitorEngine.java

class PressureException extends Exception
{
 PressureException(String msg)
 {
 super(msg);
 }
}
class TemperatureException extends Exception
{
 TemperatureException(String msg)
 {
 super(msg);
 }
}
public class MonitorEngine
{
 public static void main(String[] args)
 {
 try
 {
 monitor();
 }
 catch (Exception e)
 {
 if (e instanceof PressureException)
 System.out.println("correcting pressure problem");
 else
 System.out.println("correcting temperature problem");
 }
 }
 static void monitor() throws Exception
 {
 try
 {
 if (Math.random() < 0.1)
 throw new PressureException("pressure too high");
 else
 if (Math.random() > 0.9)
 throw new TemperatureException("temperature too high");
 else
 System.out.println("all is well");
 }
 catch (Exception e)
 {
 System.out.println(e.getMessage());
 throw e;
 }
 }
}

清單12模擬了一個(gè)實(shí)驗(yàn)性火箭發(fā)動(dòng)機(jī)的測(cè)試,以檢查發(fā)動(dòng)機(jī)的壓力或溫度是否超過(guò)了安全閾值。它通過(guò)monitor()助手方法執(zhí)行這個(gè)測(cè)試。

monitor()方法的try塊在檢測(cè)到極端壓力時(shí)拋出PressureException,在檢測(cè)到極端溫度時(shí)拋出TemperatureException。(由于這只是一個(gè)模擬,因此使用了隨機(jī)數(shù)——java.lang.Math類的靜態(tài)double random()方法返回一個(gè)介于0.0和(接近)1.0之間的隨機(jī)數(shù)。)try塊后面跟著一個(gè)catch塊,這個(gè)catch塊的目的是通過(guò)輸出警告消息來(lái)部分處理異常。然后重新拋出此異常,以便monitor()的調(diào)用方法可以完成對(duì)異常的處理。

在JDK 7之前,不能在monitor()的throws子句中指定PressureException和TemperatureException,因?yàn)閏atch塊的e參數(shù)是java.lang.Exception類型,并且重新拋出異常被視為拋出參數(shù)的類型。JDK 7及后續(xù)JDK允許在throws子句中指定這些異常類型,因?yàn)樗鼈兊木幾g器可以確定通過(guò)throw e拋出的異常來(lái)自try塊,并且只能從該塊拋出PressureException和TemperatureException。

因?yàn)楝F(xiàn)在可以指定靜態(tài)void monitor()拋出PressureException, TemperatureException,可以在調(diào)用monitor()時(shí)提供更精確的處理程序,如下面的代碼片段所示:

try
{
 monitor();
}
catch (PressureException pe)
{
 System.out.println("correcting pressure problem");
}
catch (TemperatureException te)
{
 System.out.println("correcting temperature problem");
}

由于JDK 7中的最終重新拋出提供了改進(jìn)的類型檢查,因此在以前版本的Java下編譯的源代碼可能無(wú)法在以后的JDK下編譯。例如,考慮清單13。

清單13.BreakageDemo.java

class SuperException extends Exception
{
}
class SubException1 extends SuperException
{
}
class SubException2 extends SuperException
{
}
public class BreakageDemo
{
 public static void main(String[] args) throws SuperException
 {
 try
 {
 throw new SubException1();
 }
 catch (SuperException se)
 {
 try
 {
 throw se;
 }
 catch (SubException2 se2)
 {
 }
 }
 }
}

清單13可以在JDK 6和更早版本下編譯。但是,它無(wú)法在后續(xù)版本的 JDK中編譯,因?yàn)檫@些JDK的編譯器會(huì)檢測(cè)并報(bào)告這樣一個(gè)事實(shí),即在相應(yīng)的try語(yǔ)句體中從未拋出subeexception2。這是一個(gè)小問(wèn)題,可能很少會(huì)在你的程序中遇到,讓編譯器檢測(cè)冗余代碼源是值得的。移除這些冗余代碼可以使代碼更加清晰,并且生成的類文件更小。

StackWalker和StackWalking API

通過(guò)Thread或Throwable的getStackTrace()方法獲取堆棧跟蹤代價(jià)高昂,并且會(huì)影響性能。JVM急切地捕獲整個(gè)堆棧的快照(隱藏的堆棧幀除外),即使只需要前幾個(gè)幀。此外,其代碼可能必須處理不感興趣的幀,這也很耗時(shí)。最后,無(wú)法訪問(wèn)由堆棧幀表示的方法所聲明的類的實(shí)際 java.lang.Class 實(shí)例。為訪問(wèn)這個(gè)Class對(duì)象,必須擴(kuò)展java.lang.SecurityManager以訪問(wèn)受保護(hù)的getClassContext()方法,該方法返回當(dāng)前執(zhí)行堆棧作為 Class 對(duì)象的數(shù)組。

JDK 9引入了java.lang.StackWalker類(及其嵌套的Option類和StackFrame接口),作為StackTraceElement(加上SecurityManager)的一個(gè)性能更高、功能更強(qiáng)的替代方案。

總結(jié)

本文完成了對(duì)Java異常處理的兩部分介紹。可能需要通過(guò)回顧Java教程中的Oracle異常課程來(lái)加強(qiáng)對(duì)這個(gè)的理解。另一個(gè)很好的資源是Baeldung的Java異常處理教程,其中包括異常處理中的反模式。

原文標(biāo)題:Exceptions in Java: Advanced features and types,作者:Jeff Friesen

責(zé)任編輯:華軒 來(lái)源: 51CTO
相關(guān)推薦

2023-12-11 14:19:00

Java程序異常

2018-08-20 16:25:48

編程語(yǔ)言Java異常處理

2017-09-26 11:43:12

Java異常和處理

2023-05-09 15:01:43

JavaScript編程語(yǔ)言異常處理

2010-01-05 09:26:13

.NET 4.0

2013-04-07 10:01:26

Java異常處理

2009-06-25 14:05:40

Java應(yīng)用技巧

2020-07-02 22:42:18

Java異常編程

2024-12-09 12:00:00

Python編程數(shù)據(jù)類型轉(zhuǎn)換

2011-07-05 10:20:38

java

2017-06-02 10:25:26

Java異常處理

2024-09-26 10:51:51

2012-12-21 10:48:20

Java異常

2016-12-15 13:31:20

Java異常處理經(jīng)驗(yàn)

2018-07-11 19:41:47

MySQL定義異常異常處理

2013-04-01 09:39:06

JavaJava異常

2009-01-05 09:14:17

.NETcatch性能損失

2024-02-21 12:18:00

Java虛擬機(jī)JVM

2015-03-16 16:16:15

JavaJava異常處理Java最佳實(shí)踐

2012-11-19 14:29:38

JavaJava異常異常處理
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 天天爱天天操 | 美女激情av | 久久国产激情视频 | 欧美xxxⅹ性欧美大片 | 日韩成人影院在线观看 | 欧美黄色网络 | 99精品久久久 | 99久热在线精品视频观看 | 夜夜草| 色成人免费网站 | 国产美女精品 | 精品久久国产 | 九九热精品视频 | 欧美精品一区二区三区蜜臀 | 日韩中文字幕在线 | 9191av| 欧美在线a | 亚洲成av人片在线观看 | 国产97碰免费视频 | 免费美女网站 | 亚洲一区二区精品视频 | 欧美专区在线视频 | 2022国产精品 | a级片在线观看 | 精品1区2区 | 97伦理电影网 | 欧美一级黑人aaaaaaa做受 | www.日韩| 毛片网络| 国产精品久久久久久亚洲调教 | 毛片视频观看 | 中文字幕成人av | 成人av片在线观看 | 亚洲视频一 | 亚洲区中文字幕 | 亚州综合一区 | 亚洲精品二区 | 中文字幕一区二区三区在线乱码 | 激情五月婷婷综合 | 日韩欧美一区二区三区在线播放 | 人人干人人超 |