目錄
前言
Android 性能優化(一)閃退治理、卡頓優化、耗電優化、APK瘦身 ,這篇中我強調“運行穩定大於一切”,保證程序運行中不出現Crash,要比卡頓、耗電、安裝包大小等方面更爲重要。
當一個方法發生錯誤時,此方法會產生一個對象並將其交給運行時系統。 這個對象就叫異常對象,它包含了錯誤信息、異常類型以及程序的狀態。創建一個異常對象並將其交給運行時系統稱之爲拋出異常。造成Crash的原因有很多,而在程序運行過程中Throwable拋出的異常或錯誤就是其中最普遍的成因。本篇將對Throwable下的Exception和error有一個比較清晰的認識。
一、定義
-
throwable
可拋出的意思,是根基類,子類有異常exception和錯誤error。在java中只有Throwable類型的實例才能被拋出(throw)或者捕獲(catch),它是異常處理機制的基本類型。
-
exception
表示程序運行中出現的非正常狀態,並告訴我們程序發生了什麼問題,且程序自身可以進行攔截或處理的異常。
-
error
是指程序無法處理的錯誤,其中包括程序運行時 JVM出現的問題。
二、異常類型
Throwable分爲檢查型異常(Checked Exception)和非檢查型異常(Unchecked Exception)。
- 檢查型異常必須在源碼中進行try-catch捕獲處理,這是編譯檢查的一部分。
- 類似NullPointerException,ArrayIndexOfBoundException就是非檢查型異常,通常是可以通過編碼避免的邏輯錯誤。
- 編譯期不檢查,如果拋出了非檢查型異常,那就是編碼邏輯有問題,需要開發者解決。
三、異常處理5個關鍵字
異常處理過程:一般情況下是用try來執行一段程序,如果系統會拋出(throw\throws)一個異常對象,可以通過它的類型來捕獲(catch)它,或通過總是執行代碼塊(finally)來處理。
需要注意:try-catch代碼段會產生額外的性能開銷,Java每實例化一個Exception,都會對當時的棧進行快照。
try
- 用來指定一段可能產生異常的代碼塊;是一個“監控區域”的概念。
catch
- 緊跟在try塊後面,用來指定你想要捕獲的異常的類型;
throw
- 語句用來明確地拋出一個異常;
throws
- 用來聲明一個方法可能拋出的各種異常;
finally
- 爲確保一段代碼不管發生什麼異常狀況都要被執行;
四、模擬面試
面試題1:try語句可以嵌套嗎?
答:可以的。每當遇到一個try語句,指定的一段可能產生異常的代碼塊就會被放入異常棧中,直到所有的try語句都完成。如果下一級的try語句沒有對某種異常進行處理(catch),異常棧就會執行出棧操作,交給上一層處理,直到遇到有處理這種異常的try語句或者最終將異常拋給JVM。
面試題2:下面代碼有問題嗎?
try {
Thread.sleep(500L);
} catch (Exception e) {
}
答:這段代碼違反了異常處理的兩個基本原則。 第一,儘量不要捕獲類似Exception這樣的通用異常,而是應該捕獲特定異常。例如,這裏Thread.sleep() 應當捕獲 InterruptedException。第二,在異常處理(catch)中不要生吞(swallow)異常。這是特別注意的事情,因爲很可能會導致非常難以診斷的詭異情況。
面試題3:try塊中加退出語句return會怎樣?
try塊中有return語句時,仍然會執行finally塊中的語句,然後方法再返回。
五、異常的棧軌跡(Stack Trace)
參考:Java異常的棧軌跡(Stack Trace) - wawlian - 博客園
1、printStackTrace()
Exception類本身除了定義了幾個構造器之外,所有的方法都是從其父類Throwable繼承過來的,而且和異常相關的方法都是從父類繼承過來的。其中就有一個 printStackTrace() 方法。
這個方法會將Throwable對象的棧軌跡信息打印到標準錯誤輸出流上。輸出的大體樣子如下:
java.lang.NullPointerException
at MyClass.mash(MyClass.java:9)
at MyClass.crunch(MyClass.java:6)
at MyClass.main(MyClass.java:3)
輸出的第一行是toString()方法的輸出,後面幾行的內容都是之前通過fillInStackTrace()方法保存的內容。
2、getStackTrace()方法
對printStackTrace()方法所打印信息進行訪問。它會返回一個棧軌跡元素的數組 StackTraceElement[]。
下面是一個使用getStackTrace()訪問這些軌跡棧元素並打印輸出的例子:
public class TestPrintStackTrace {
public static void f() throws Exception{
throw new Exception("出問題啦!");
}
public static void g() throws Exception{
f();
}
public static void main(String[] args) {
try {
g();
}catch(Exception e) {
System.out.println("e.printStackTrace:");
e.printStackTrace();
System.out.println("e.getStackTrace:");
for(StackTraceElement elem : e.getStackTrace()) {
System.out.println(elem);
}
}
}
}
getStackTrace()的輸出和printStackTrace()的輸出基本上是一樣的,如下:
e.printStackTrace:
java.lang.Exception: 出問題啦!
at TestPrintStackTrace.f(TestPrintStackTrace.java:3)
at TestPrintStackTrace.g(TestPrintStackTrace.java:6)
at TestPrintStackTrace.main(TestPrintStackTrace.java:10)
e.getStackTrace:
TestPrintStackTrace.f(TestPrintStackTrace.java:3)
TestPrintStackTrace.g(TestPrintStackTrace.java:6)
TestPrintStackTrace.main(TestPrintStackTrace.java:10)
3、fillInStackTrace()
我們在前面也提到了這個方法。要說清楚這個方法,首先要講一下捕獲異常之後重新拋出的問題。
首先,在catch代碼塊中捕獲到異常,調用printStackTrace()打印棧軌跡,又重新throw出去。在上一級的方法調用中,再捕獲這個異常並且打印出棧軌跡信息,這兩個棧軌跡信息會一樣嗎?我們看一下代碼:
public class TestPrintStackTrace {
public static void f() throws Exception{
throw new Exception("出問題啦!");
}
public static void g() throws Exception{
try {
f();
}catch(Exception e) {
e.printStackTrace();
throw e;
}
}
public static void main(String[] args) {
try {
g();
}catch(Exception e) {
e.printStackTrace();
}
}
}
在main方法中調用g()方法,並且捕獲拋出的異常。此時 f()方法拋出異常“出問題啦!”,按理說兩次打印棧軌跡的信息應該不同,第二次打印的信息應該沒有關於f()的信息。輸出結果如下:事實上,兩次打印棧軌跡信息是一樣的!
java.lang.Exception: 出問題啦!
at TestPrintStackTrace.f(TestPrintStackTrace.java:3)
at TestPrintStackTrace.g(TestPrintStackTrace.java:7)
at TestPrintStackTrace.main(TestPrintStackTrace.java:16)
java.lang.Exception: 出問題啦!
at TestPrintStackTrace.f(TestPrintStackTrace.java:3)
at TestPrintStackTrace.g(TestPrintStackTrace.java:7)
at TestPrintStackTrace.main(TestPrintStackTrace.java:16)
因此,當捕獲到異常立即拋出,在上級方法調用中再次捕獲這個異常,兩次打印的棧軌跡信息是一樣的。
因爲,沒有將當前線程當前狀態下的軌跡棧的狀態保存進Throwabe中。而fillInStackTrace()方法剛好做的就是這樣的保存工作。
public Throwable fillInStackTrace()
可見,fillInStackTrace()方法的返回值是保存了當前棧軌跡信息的Throwable對象。g()方法代碼改動如下:
public static void g() throws Exception{
try {
f();
}catch(Exception e) {
e.printStackTrace();
throw (Exception)e.fillInStackTrace();
}
}
輸出結果:
java.lang.Exception: 出問題啦!
at TestPrintStackTrace.f(TestPrintStackTrace.java:3)
at TestPrintStackTrace.g(TestPrintStackTrace.java:7)
at TestPrintStackTrace.main(TestPrintStackTrace.java:17)
java.lang.Exception: 出問題啦!
at TestPrintStackTrace.g(TestPrintStackTrace.java:11)
at TestPrintStackTrace.main(TestPrintStackTrace.java:17)
我們看到,在main方法中打印棧軌跡已經沒有了f()相關的信息了。