你有沒有這樣的印象,當你想要更新一款 APP 的時候,它的更新日誌裏總有這麼一兩句描述:java
做爲一名負責任的程序員,咱們固然但願程序不會出現 bug,由於 bug 出現的越多,間接地證實了咱們的編程能力越差,至少領導是這麼看的。程序員
事實上,領導是不會拿本身的腦殼宣言的:「咱們的程序毫不存在任何一個 bug。」但當程序出現 bug 的時候,領導會堅決果斷地選擇讓程序員背鍋。數據庫
爲了讓本身少背鍋,咱們能夠這樣作:編程
還有一點須要作的是,在敲代碼以前,學習必要的編程常識,作到兵馬未動,糧草先行。性能
在 Java 中,異常(Throwable)的層次結構大體以下。學習
Error 類異常描述了 Java 運行時系統的內部錯誤,好比最多見的 OutOfMemoryError
和 NoClassDefFoundError
。測試
致使 OutOfMemoryError
的常見緣由有如下幾種:編碼
OutOfMemoryError
的解決辦法須要視狀況而定,但問題的根源在於程序的設計不夠合理,須要經過一些性能檢測才能找得出引起問題的根源。spa
致使 NoClassDefFoundError
的緣由只有一個,Java 虛擬機在編譯時能找到類,而在運行時卻找不到。設計
NoClassDefFoundError
的解決辦法,我截了一張圖,如上所示。當一個項目引用了另一個項目時,切記這一步!
Exception(例外)一般可分爲兩類,一類是寫代碼的人形成的,好比訪問空指針(NullPointerException
)。應當在敲代碼的時候進行檢查,以杜絕這類異常的發生。
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();
}
}
複製代碼
當拋出異常的時候,剩餘的代碼就會終止執行,這時候一些資源就須要主動回收。Java 的解決方案就是 finally
子句——無論異常有沒有被捕獲,finally
子句裏的代碼都會執行。
在下面的示例當中,輸入流將會被關閉,以釋放資源。
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 {}
複製代碼
也就是說,調用 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();
}
}
}
複製代碼
總之,咱們須要另一種更優雅的解決方案。JDK7 新增了 Try-With-Resource
語法:若是一個類(好比 InputStream
)實現了 AutoCloseable
接口,那麼就能夠將該類的對象建立在 try
關鍵字後面的括號中,當 try-catch
代碼塊執行完畢後,Java 會確保該對象的 close
方法被調用。示例以下。
public static void main(String[] args) {
try (InputStream is = new FileInputStream("沉默王二.txt")) {
int b;
while ((b = is.read()) != -1) {
}
} catch (IOException e) {
e.printStackTrace();
}
}
複製代碼
關於異常處理機制的使用,我這裏總結了一些很是實用的建議,但願你可以採納。
1)儘可能捕獲原始的異常。
實際應該捕獲 FileNotFoundException
,卻捕獲了泛化的 Exception
。示例以下。
InputStream is = null;
try {
is = new FileInputStream("沉默王二.txt");
} catch (Exception e) {
e.printStackTrace();
}
複製代碼
這樣作的壞處顯而易見:假如你喊「王二」,那麼我就敢答應;假如你喊「老王」,那麼我還真不敢答應,萬一你喊的我妹妹「王三」呢?
不少初學者誤覺得捕獲泛化的 Exception
更省事,但也更容易讓人「丈二和尚摸不着頭腦」。相反,捕獲原始的異常可以讓協做者更輕鬆地辨識異常類型,更容易找出問題的根源。
2)儘可能不要打印堆棧後再拋出異常
當異常發生時打印它,而後從新拋出它,以便調用者可以適當地處理它。就像下面這段代碼同樣。
public static void main(String[] args) throws IOException {
try (InputStream is = new FileInputStream("沉默王二.txt")) {
}catch (IOException e) {
e.printStackTrace();
throw e;
}
}
複製代碼
這彷佛考慮得很周全,可是這樣作的壞處是調用者可能也打印了異常,重複的打印信息會增添排查問題的難度。
java.io.FileNotFoundException: 沉默王二.txt (系統找不到指定的文件。)
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 (系統找不到指定的文件。)
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
的,結果使用了異常處理機制來代替。
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 萬次的循環,代碼片斷 A(異常處理機制)執行的時間大概須要 1983 毫秒;代碼片斷 B(正常判斷)執行的時間大概只須要 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
就會出現。
java.io.FileNotFoundException: 沉默王二.txt (系統找不到指定的文件。)
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
並非程序出現問題的本因,但實際上它出現了,無形當中干擾了咱們的視線。正確的作法是延遲捕獲異常,讓程序在第一個異常捕獲後就終止執行。
好了,關於異常咱們就說到這。異常處理是程序開發中必不可少的操做之一,但如何正確優雅地對異常進行處理倒是一門學問,好的異常處理機制能夠確保程序的健壯性,提升系統的可用率。