Java中的異常

本文基于JDK 1.8.0_45

Throwable架構(gòu)是非常經(jīng)典的繼承結(jié)構(gòu):

  1. 所有的廣意的異常都是Throwable的子類,通過繼承的樹狀結(jié)構(gòu)來對(duì)異常進(jìn)行分類,比如異常分為兩大類,Error和Exception,其中Exception又分為檢查異常和運(yùn)行期異常,每一個(gè)分支又是細(xì)化的一類異常。
  2. 在Throwable中定義大多數(shù)關(guān)于異常處理的方法,并提供部分接口給子類重寫。
  3. 一般都可以從異常命名中看出來其作用是什么,因此當(dāng)我們自定義異常的時(shí)候也最好遵循這一命名規(guī)則。
  4. printStackTrace方法有PrintWriter和PrintStream兩種參數(shù)的重載方法,源碼中通過自定義的PrintStreamOrWriter內(nèi)部類的兩種子類分別對(duì)這兩種類型的參數(shù)進(jìn)行封裝,并通過統(tǒng)一接口提供lock和println的方法,通過這種方式避免了復(fù)制代碼來分別實(shí)現(xiàn)相似的功能。該方法通過遞歸遍歷并打印自己的異常堆棧、suppressed異常堆棧和cause異常堆棧。
  5. 從1.7版本開始添加了兩個(gè)API方法:addSuppressed和getSuppressed來解決存在多個(gè)異常拋出的場(chǎng)景(如try-with-resources)。其中try-with-resources的字節(jié)碼大概是下面的樣子,有可能會(huì)出現(xiàn)兩個(gè)異常,一個(gè)是try塊中的異常,一個(gè)是調(diào)用close方法時(shí)拋出的異常。使用suppressed就可以避免前一個(gè)異常被丟棄而無法找到真正的異常原因的問題。
try{
  throw new Throwable();
}catch(Throwable e) {
  try {
    resource.close();
  }catch(Throwable suppressedException) {
    e.addSuppressed(suppressedException);
    throw e;
  }
  throw suppressedException;
}

Throwable

Throwable提供五種構(gòu)造方法來構(gòu)造Throwable,可以傳入具體信息,發(fā)生原因等,一般異常均為根據(jù)需要繼承實(shí)現(xiàn)這五種構(gòu)造方法或某幾種構(gòu)造方法,如Exception和Error均為直接實(shí)現(xiàn)這五種構(gòu)造方法。

public Throwable() {...}

public Throwable(String message) {...}

public Throwable(String message, Throwable cause) {...}

public Throwable(Throwable cause) {...}

public Throwable(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {...}

另外Throwable還提供了豐富的方法,比如getLocalizedMessage讓異常提供本地化的異常信息,很多StackTrace相關(guān)的方法讓用戶可以很好的獲取、操作StackTrace。

WARNING, 有時(shí)候我們會(huì)通過getCause或getSuppressed方法來做一些個(gè)性化的處理,由于該方法會(huì)返回一個(gè)新的Throwable對(duì)象,因此在處理過程中經(jīng)常會(huì)使用遞歸或循環(huán)的方式遍歷到盡頭,但是在遍歷的過程中一定要小心循環(huán)依賴的問題,否則會(huì)導(dǎo)致系統(tǒng)資源耗盡而出嚴(yán)重問題。從以下代碼在執(zhí)行到exception1.printStackTrace();時(shí)的打印信息中可以看到CIRCULAR REFERENCE,這是因?yàn)閑xception2在new的時(shí)候指定它的cause是exception1,而在exception1.initCause(exception2);又指定exception1的cause是exception2,從而出現(xiàn)了異常cause的循環(huán)依賴。

package com.oomlife.java.example;

public class ExceptionExample {
    public static void main(String[] args) {
        FakeException exception1 = new FakeException();
        FakeFakeException exception2 = new FakeFakeException(exception1);
        exception1.initCause(exception2);

        exception1.printStackTrace();
    }

    public static class FakeException extends RuntimeException {
    }

    public static class FakeFakeException extends RuntimeException {
        public FakeFakeException(Throwable cause) {
            super(cause);
        }
    }
}


打印結(jié)果:
com.oomlife.java.example.ExceptionExample$FakeException
    at com.oomlife.java.example.ExceptionExample.main(ExceptionExample.java:5)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Caused by: com.oomlife.java.example.ExceptionExample$FakeFakeException: com.oomlife.java.example.ExceptionExample$FakeException
    at com.oomlife.java.example.ExceptionExample.main(ExceptionExample.java:6)
    ... 5 more
    [CIRCULAR REFERENCE:com.oomlife.java.example.ExceptionExample$FakeException]

StackTraceElement類是異常堆棧信息的類,它提供了所在class、所在方法、所在文件名和行號(hào)等信息,一般每個(gè)實(shí)例是異常堆棧中的一行,當(dāng)使用Java實(shí)現(xiàn)自己的DSL的時(shí)候,這個(gè)類對(duì)實(shí)現(xiàn)自己的異常跟蹤和打印有很大的借鑒意義。

Exception

Java中的異常分為兩種,受檢查的異常和運(yùn)行期異常。

受檢查的異常在代碼中必須進(jìn)行顯式聲明或處理,否則代碼無法編譯通過,比如SQLException。

運(yùn)行期的異常是指在Java程序執(zhí)行的過程中由于一些特殊的情況導(dǎo)致的異常,比如ClassCastException是將一個(gè)對(duì)象強(qiáng)制轉(zhuǎn)為另一個(gè)不相關(guān)的對(duì)象時(shí)拋出的異常,再比如ArrayIndexOutOfBoundsException是在運(yùn)行期訪問數(shù)組的一個(gè)非法索引時(shí)拋出的異常,這些異常大都是在寫代碼的時(shí)候很難預(yù)判或進(jìn)行預(yù)先檢查的成本太高,比如NullPointerException。

Java API中提供了種類繁多的異常類型,在實(shí)際應(yīng)用中,我們可以直接使用Java API中提供的異常,也可以自定義異常。在自定義異常的時(shí)候最好遵從Java API中的一些最佳實(shí)踐:

  1. 將自定義異常區(qū)分為受檢查的異常和運(yùn)行期異常;
  2. 如果需要將受檢查異常捕獲并重新拋出運(yùn)行期異常時(shí)要慎重考慮是否真的有必要將異常處理延后到運(yùn)行期;
  3. 在重寫的構(gòu)造方法中一定要恰當(dāng)調(diào)用父類的構(gòu)造方法來封裝異常的詳細(xì)信息,避免添加重復(fù)的字段;
  4. 異常命名要是自描述的,從名字即可很容易的看出來異常的用途是什么。

Error


Error具有和Exception相同的結(jié)構(gòu),一般用于指代那些嚴(yán)重的無法被處理的錯(cuò)誤,如運(yùn)行時(shí)編譯錯(cuò)誤,系統(tǒng)錯(cuò)誤等,以下是兩個(gè)Java內(nèi)置錯(cuò)誤的例子:

LinkageError


這個(gè)錯(cuò)誤是class相關(guān)的錯(cuò)誤,比如在編譯成功后class被錯(cuò)誤修改了,有環(huán)形引用等。這是一個(gè)受檢查的錯(cuò)誤,主要是被編譯器捕獲。另外如果一個(gè)class的定義在當(dāng)前執(zhí)行方法最后一次編譯以后作了不兼容的更改,則此錯(cuò)誤可能在運(yùn)行期發(fā)生。包括ExceptionInInitializerError、NoClassDefFoundError、IncompatibleClassChangeError、ClassCircularityError、ClassFormatError(包括GenericSignatureFormatError、UnsupportedClassVersionError)、VerifyError、BootstrapMethodError和UnsatisfiedLinkError,其中IncompatibleClassChangeError系列包括AbstractMethodError、IllegalAccessError、InstantiationError、NoSuchFieldError和NoSuchMethodError。下面是一個(gè)下運(yùn)行期的AbstractMethodError的例子:

public class AbstractClass {
  public void hello() {
    System.out.println("Hello, I'm an abstract class.");
  }
}
public class MainClass extends AbsClass {
  public static void main(String[] args) {
    MainClass cl = new MainClass();
    cl.hello();
  }
}

比如我先編譯以上兩個(gè)class并執(zhí)行,結(jié)果如下:

> javac AbstractClass.java
> javac MainClass.java
> java MainClass
Hello, I'm an abstract class.

一切正常,然后我們修改AbstractClass如下:

public class AbstractClass {
  public abstract void hello();
}

重新編譯AbstractClass并執(zhí)行,結(jié)果如下:

> javac AbstractClass.java
> java MainClass
Exception in thread "main" java.lang.AbstractMethodError:
MainClass.hello()V
        at MainClass.main(MainClass.java:4)

VirtualMachineError


這是JVM相關(guān)的錯(cuò)誤,包括OutOfMemoryError(發(fā)生于內(nèi)存不足的情況)、StackOverflowError(比如非尾遞歸的遞歸方法調(diào)用的時(shí)候可能會(huì)發(fā)生)、InternalError,還有其他未知錯(cuò)誤UnknownError。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 如果我們制作一個(gè)計(jì)算器程序,當(dāng)用戶輸入的除數(shù)為0時(shí),程序?qū)?huì)崩潰直接退出,那么該程序的用戶體驗(yàn)將會(huì)非常差。我們應(yīng)該...
    yuluo閱讀 663評(píng)論 0 1
  • 兩類異常 Java中的異常繼承體系為: 這里的異常( Exception)分為兩大類:Checked異常和Runt...
    tobe_superman閱讀 685評(píng)論 0 0
  • 我們通過一個(gè)簡(jiǎn)單的實(shí)例程序來了解一下什么是java中的異常處理 使用try,catch 看下面這個(gè)程序: 這個(gè)程序...
    六尺帳篷閱讀 1,427評(píng)論 0 7
  • 數(shù)十年來,江湖上從沒有人敢悖逆理發(fā)六十的規(guī)矩,登哪山,拜哪門,你吃這碗飯,就得先給自己立這個(gè)檻。 可一年前彼得在人...
    將要遠(yuǎn)行閱讀 787評(píng)論 1 2
  • 昨天夜里,大約兩點(diǎn)鐘的時(shí)候。隔壁床的姑娘把我叫醒了,說院子里有動(dòng)靜。我仔細(xì)一聽,確實(shí)有什么東西在呼呼作響。...
    二喬木閱讀 152評(píng)論 0 0

友情鏈接更多精彩內(nèi)容