Java:優(yōu)雅地處理異常真是一門學問??!-創(chuàng)新互聯(lián)

本篇我們來談談如何優(yōu)雅地處理異常。

創(chuàng)新互聯(lián)公司于2013年開始,先為南山等服務建站,南山等地企業(yè),進行企業(yè)商務咨詢服務。為南山企業(yè)網(wǎng)站制作PC+手機+微官網(wǎng)三網(wǎng)同步一站式服務解決您的所有建站問題。

01、異常處理機制可以少出 bug

你有沒有這樣的印象,當你想要更新一款 APP 的時候,它的更新日志里總有這么一兩句描述:

  • 修復若干 bug
  • 殺了某程序員祭天,并成功解決掉他遺留的 bug

作為一名負責任的程序員,我們當然希望程序不會出現(xiàn) bug,因為 bug 出現(xiàn)的越多,間接地證明了我們的編程能力越差,至少領導是這么看的。

事實上,領導是不會拿自己的腦袋宣言的:“我們的程序絕不存在任何一個 bug?!钡敵绦虺霈F(xiàn) bug 的時候,領導會毫不猶豫地選擇讓程序員背鍋。

為了讓自己少背鍋,我們可以這樣做:

  • 在編碼階段合理使用異常處理機制,并記錄日志以備后續(xù)分析
  • 在測試階段進行大量有效的測試,在用戶發(fā)現(xiàn)錯誤之前發(fā)現(xiàn)錯誤

還有一點需要做的是,在敲代碼之前,學習必要的編程常識,做到兵馬未動,糧草先行。

02、異常分類

在 Java 中,異常(Throwable)的層次結(jié)構(gòu)大致如下。

Java:優(yōu)雅地處理異常真是一門學問??!

Error 類異常描述了 Java 運行時系統(tǒng)的內(nèi)部錯誤,比如最常見的OutOfMemoryErrorNoClassDefFoundError。

導致OutOfMemoryError的常見原因有以下幾種:

  • 內(nèi)存中加載的數(shù)據(jù)量過于龐大,如一次從數(shù)據(jù)庫取出過多數(shù)據(jù);
  • 集合中的對象引用在使用完后未清空,使得 JVM 不能回收;
  • 代碼中存在死循環(huán)或循環(huán)產(chǎn)生過多重復的對象;
  • 啟動參數(shù)中內(nèi)存的設定值過?。?/li>

OutOfMemoryError的解決辦法需要視情況而定,但問題的根源在于程序的設計不夠合理,需要通過一些性能檢測才能找得出引發(fā)問題的根源。

導致NoClassDefFoundError的原因只有一個,Java 虛擬機在編譯時能找到類,而在運行時卻找不到。

Java:優(yōu)雅地處理異常真是一門學問??!

NoClassDefFoundError的解決辦法,我截了一張圖,如上所示。當一個項目引用了另外一個項目時,切記這一步!

Exception(例外)通??煞譃閮深悾活愂菍懘a的人造成的,比如訪問空指針(NullPointerException)。應當在敲代碼的時候進行檢查,以杜絕這類異常的發(fā)生。

if (str == null || "".eqauls(str)) {
}

另外一類異常不是寫代碼的人造成的,要么需要拋出,要么需要捕獲,比如說常見的IOException。

拋出的示例。

public static void main(String[] args) throws IOException {
    InputStream is = new FileInputStream("沉默王二.txt");
    int b;
    while ((b = is.read()) != -1) {

    }
}

捕獲的示例。

public static void main(String[] args) {
    try {
        InputStream is = new FileInputStream("沉默王二.txt");
        int b;
        while((b = is.read()) != -1) {

        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

03、finally

當拋出異常的時候,剩余的代碼就會終止執(zhí)行,這時候一些資源就需要主動回收。Java 的解決方案就是finally子句——不管異常有沒有被捕獲,finally子句里的代碼都會執(zhí)行。

在下面的示例當中,輸入流將會被關(guān)閉,以釋放資源。

public static void main(String[] args) {
    InputStream is = null;
    try {
        is = new FileInputStream("沉默王二.txt");
        int b;
        while ((b = is.read()) != -1) {}
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        is.close();
    }
}

但我總覺得這樣的設計有點問題,因為close()方法同樣會拋出IOException

public void close() throws IOException {}

也就是說,調(diào)用close()的 main 方法要么需要拋出IOException,要么需要在finally子句里重新捕獲IOException

選擇前一種就會讓try catch略顯尷尬,就像下面這樣。

public static void main(String[] args) throws IOException {
    InputStream is = null;
    try {
        is = new FileInputStream("沉默王二.txt");
        int b;
        while ((b = is.read()) != -1) {}
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        is.close();
    }
}

選擇后一種會讓代碼看起來很臃腫,就像下面這樣。

public static void main(String[] args) {
    InputStream is = null;
    try {
        is = new FileInputStream("沉默王二.txt");
        int b;
        while ((b = is.read()) != -1) {}
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

總之,我們需要另外一種更優(yōu)雅的解決方案。JDK7 新增了Try-With-Resource語法:如果一個類(比如InputStream)實現(xiàn)了AutoCloseable接口,那么就可以將該類的對象創(chuàng)建在try關(guān)鍵字后面的括號中,當try-catch代碼塊執(zhí)行完畢后,Java 會確保該對象的close方法被調(diào)用。示例如下。

public static void main(String[] args) {
    try (InputStream is = new FileInputStream("沉默王二.txt")) {
        int b;
        while ((b = is.read()) != -1) {
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

04、實用建議

關(guān)于異常處理機制的使用,我這里總結(jié)了一些非常實用的建議,希望你能夠采納。

1)盡量捕獲原始的異常

實際應該捕獲FileNotFoundException,卻捕獲了泛化的Exception。示例如下。

InputStream is = null;
try {
    is = new FileInputStream("沉默王二.txt");
} catch (Exception e) {
    e.printStackTrace();
}

這樣做的壞處顯而易見:假如你喊“王二”,那么我就敢答應;假如你喊“老王”,那么我還真不敢答應,萬一你喊的我妹妹“王三”呢?

很多初學者誤以為捕獲泛化的Exception更省事,但也更容易讓人“丈二和尚摸不著頭腦”。相反,捕獲原始的異常能夠讓協(xié)作者更輕松地辨識異常類型,更容易找出問題的根源。

2)盡量不要打印堆棧后再拋出異常

當異常發(fā)生時打印它,然后重新拋出它,以便調(diào)用者能夠適當?shù)靥幚硭?。就像下面這段代碼一樣。

public static void main(String[] args) throws IOException {
    try (InputStream is = new FileInputStream("沉默王二.txt")) {
    }catch (IOException e) {
        e.printStackTrace();
        throw e;
    } 
}

這似乎考慮得很周全,但是這樣做的壞處是調(diào)用者可能也打印了異常,重復的打印信息會增添排查問題的難度。

java.io.FileNotFoundException: 沉默王二.txt (系統(tǒng)找不到指定的文件。)
    at java.io.FileInputStream.open0(Native Method)
    at java.io.FileInputStream.open(FileInputStream.java:195)
    at java.io.FileInputStream.<init>(FileInputStream.java:138)
    at java.io.FileInputStream.<init>(FileInputStream.java:93)
    at learning.Test.main(Test.java:10)
Exception in thread "main" java.io.FileNotFoundException: 沉默王二.txt (系統(tǒng)找不到指定的文件。)
    at java.io.FileInputStream.open0(Native Method)
    at java.io.FileInputStream.open(FileInputStream.java:195)
    at java.io.FileInputStream.<init>(FileInputStream.java:138)
    at java.io.FileInputStream.<init>(FileInputStream.java:93)
    at learning.Test.main(Test.java:10)

3)千萬不要用異常處理機制代替判斷

我曾見過類似下面這樣奇葩的代碼,本來應該判null的,結(jié)果使用了異常處理機制來代替。

public static void main(String[] args) {
    try {
        String str = null;
        String[] strs = str.split(",");
    } catch (NullPointerException e) {
        e.printStackTrace();
    }
}

捕獲異常相對判斷花費的時間要多得多!我們可以模擬兩個代碼片段來對比一下。

代碼片段 A:

long a = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
    try {
        String str = null;
        String[] strs = str.split(",");
    } catch (NullPointerException e) {
    }
}
long b = System.currentTimeMillis();
System.out.println(b - a);

代碼片段 B:

long a = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
    String str = null;
    if (str != null) {
        String[] strs = str.split(",");
    }
}
long b = System.currentTimeMillis();
System.out.println(b - a);

100000 萬次的循環(huán),代碼片段 A(異常處理機制)執(zhí)行的時間大概需要 1983 毫秒;代碼片段 B(正常判斷)執(zhí)行的時間大概只需要 1 毫秒。這樣的比較雖然不夠精確,但足以說明問題。

4)不要盲目地過早捕獲異常

如果盲目地過早捕獲異常的話,通常會導致更嚴重的錯誤和其他異常。請看下面的例子。

InputStream is = null;
try {
    is = new FileInputStream("沉默王二.txt");

} catch (FileNotFoundException e) {
    e.printStackTrace();
}

int b;
try {
    while ((b = is.read()) != -1) {
    }
} catch (IOException e) {
    e.printStackTrace();
}

finally {
    try {
        is.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

假如文件沒有找到的話,InputStream的對象引用 is 就為null,新的NullPointerException就會出現(xiàn)。

java.io.FileNotFoundException: 沉默王二.txt (系統(tǒng)找不到指定的文件。)
    at java.io.FileInputStream.open0(Native Method)
    at java.io.FileInputStream.open(FileInputStream.java:195)
    at java.io.FileInputStream.<init>(FileInputStream.java:138)
    at java.io.FileInputStream.<init>(FileInputStream.java:93)
    at learning.Test.main(Test.java:12)
Exception in thread "main" java.lang.NullPointerException
    at learning.Test.main(Test.java:28)

NullPointerException并不是程序出現(xiàn)問題的本因,但實際上它出現(xiàn)了,無形當中干擾了我們的視線。正確的做法是延遲捕獲異常,讓程序在第一個異常捕獲后就終止執(zhí)行。

05、最后

好了,關(guān)于異常我們就說到這。異常處理是程序開發(fā)中必不可少的操作之一,但如何正確優(yōu)雅地對異常進行處理卻是一門學問,好的異常處理機制可以確保程序的健壯性,提高系統(tǒng)的可用率。

上一篇:Java面試官:兄弟,你確定double精度比float低嗎?

下一篇:再談 Java 的繼承和超類 Object

創(chuàng)新互聯(lián)www.cdcxhl.cn,專業(yè)提供香港、美國云服務器,動態(tài)BGP最優(yōu)骨干路由自動選擇,持續(xù)穩(wěn)定高效的網(wǎng)絡助力業(yè)務部署。公司持有工信部辦法的idc、isp許可證, 機房獨有T級流量清洗系統(tǒng)配攻擊溯源,準確進行流量調(diào)度,確保服務器高可用性。佳節(jié)活動現(xiàn)已開啟,新人活動云服務器買多久送多久。

標題名稱:Java:優(yōu)雅地處理異常真是一門學問?。?創(chuàng)新互聯(lián)
新聞來源:http://bm7419.com/article30/cdgpso.html

成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供自適應網(wǎng)站、Google、品牌網(wǎng)站設計、用戶體驗、網(wǎng)站排名、ChatGPT

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

微信小程序開發(fā)