全部代碼均在本地編譯運行測試,環境爲 Windows7 32位機器 + eclipse Mars.2 Release (4.5.2)java
2016-10-17 整理程序員
1 class smallT { 2 public static void main(String args[]) { 3 smallT t = new smallT(); 4 int b = t.get(); 5 System.out.println(b); 6 } 7 8 public int get() { 9 try { 10 return 1; 11 } finally { 12 return 2; 13 } 14 } 15 }
衆所周知,在一個try-finally 語句中,finally 語句塊老是在控制權離開try 語句塊時執行的,可是finally中若是有return則try中的return結果再也不返回給主調者,因此返回的結果是2。即,若是finally裏也有返回語句,那麼以finally的爲主。也就是說,千萬不要用一個return、break、continue 或throw 來退出一個finally 語句塊,且千萬不要容許將一個受檢查的異常傳播到一個finally 語句塊以外去。容易出一些潛在bug。
緣由:查看字節碼算法
public int get(); Code: 0: goto 4 3: pop 4: iconst_2 5: ireturn Exception table: from to target type 0 3 3 any
get方法執行開始,直接產生了一個goto指令跳轉到了4處,iconst_2指令將常量2壓入操做數棧,跳過了pop指令。而後執行ireturn指令,從當前方法返回2。編程
1 public class Test1 { 2 public static void main(String[] args) { 3 System.out.println(new Test1().test()); 4 } 5 6 static int test() { 7 int x = 1; 8 try { 9 return x; 10 } finally { 11 ++x; 12 } 13 } 14 }
執行try時,遇到return語句,且還有finally語句塊,那麼程序此時必定要到finally執行,可是在轉去finally語句塊以前,try中先把要返回的結果(雖然準備好了返回值,可是還沒返回呢,因此我說是執行的中間)存放到不一樣於X的局部變量中,執行完finally後,在從局部變量中取出原返回結果,所以,即便finally中對變量x進行了改變,可是不會影響返回結果。由於方法內使用棧保存返回值。結論:此時finally必定會執行,且在return前執行,並返回1,
詳細緣由看字節碼:數組
static int test(); Code: 0: iconst_1 1: istore_0 2: iload_0 3: istore_2 4: iinc 0, 1 7: iload_2 8: ireturn 9: astore_1 10: iinc 0, 1 13: aload_1 14: athrow
iconst_1: 常數1進棧, istore_0: 棧頂元素1出棧,並把 1 保存在本地變量表的第1個位置裏(下標爲0的位置),iload_0 將本地變量表第一個位置的 1 推送至棧頂, istore_2: 棧頂元素 1 出棧,並把 1 保存在本地變量表的第3個位置裏(下標爲2的位置),iinc指令對本地變量表的第一個位置元素+1,iload_2 將本地變量表第3個位置的 1 推送至棧頂,準備執行ireturn指令返回……安全
純粹的概念問題,下面總結下:網絡
在Java中異常被當作對象來處理,根類是java.lang.Throwable類,在Java中定義了不少異常類(如OutOfMemoryError、NullPointerException、IndexOutOfBoundsException等),這些異常類分爲兩大類:Error和Exception。app
Error是沒法處理的異常,好比OutOfMemoryError,通常發生這種異常,JVM會選擇終止程序。所以咱們編寫程序時不須要關心這類異常。Exception,也就是咱們常常見到的一些異常狀況,好比NullPointerException、IndexOutOfBoundsException,這些異常是咱們能夠處理的異常。Exception類的異常包括checked exception和unchecked exception(unchecked exception也稱運行時異常RuntimeException,固然這裏的運行時異常並非說的運行期間的異常,只是Java中用運行時異常這個術語來表示,Exception類的異常都是在運行期間發生的)。eclipse
unchecked exception(非檢查異常),也稱運行時異常(RuntimeException),好比常見的NullPointerException、IndexOutOfBoundsException。對於運行時異常,java編譯器不要求必須進行異常捕獲處理或者拋出聲明,由程序員自行決定。 checked exception(檢查異常),也稱非運行時異常(運行時異常之外的異常就是非運行時異常),java編譯器強制程序員必須進行捕獲處理,好比常見的IOExeption和SQLException。對於非運行時異常若是不進行捕獲或者拋出聲明處理,編譯都不會經過ide
圖中紅色部分爲受檢查異常。它們必須被捕獲,或者在函數中聲明爲拋出該異常。
首先是foo(0),在try代碼塊中未拋出異常,finally是不管是否拋出異常是一定執行的語句,因此 output += 「3」;而後是 output += 「4」; 執行foo(1)的時候,try代碼塊拋出異常,進入catch代碼塊,output += 「2」;finally是必執行的,即便return也會執行output += 「3」,因爲catch代碼塊中有return語句,最後一個output += 「4」不會執行。因此結果是3423。
import java.io.IOException; class Arcane1 { public static void main(String[] args) { try { System.out.println("Hello world"); } catch (IOException e) { System.out.println("I've never seenprintln fail!"); } } }
好像看起來應該是能夠編譯的,可是本程序編譯期間錯誤。try 子句執行打印的I/O操做,而且catch 子句捕獲 IOException 異常。可是這不能編譯,由於println 方法沒有聲明會拋出任何被檢查異常,而 IOException 卻正是一個被檢查異常。Exception 類的異常包括 checked exception 和 unchecked exception(unchecked exception也稱運行時異常RuntimeException)Java語言規範中描述道:若是一個catch 子句要捕獲一個類型爲 E 的被檢查異常,而其相對應的try 子句如不能拋出E 的某種子類型的異常,那麼這就是一個編譯期錯誤。
補充一個println方法的源碼。
public void println(String x) { synchronized (this) { print(x); newLine(); } }
class Arcane2 { public static void main(String[] args) { try { } catch (Exception e) { System.out.println("This can't happen"); } } }
看起來應該是不能夠編譯的,可是它卻能夠編譯經過。由於它惟一的catch 子句檢查了Exception,捕獲 Exception 或 Throwble 的 catch 子句是合法的,無論與其相對應的 try 子句的內容爲什麼。儘管Arcane2 是一個合法的程序,可是catch 子句的內容永遠的不會被執行,這個程序什麼都不會打印。
1 interface Type1 { 2 void f() throws CloneNotSupportedException; 3 } 4 5 interface Type2 { 6 void f() throws InterruptedException; 7 } 8 9 interface Type3 extends Type1, Type2 { 10 } 11 12 class Arcane3 implements Type3 { 13 public void f() { 14 System.out.println("Hello world"); 15 } 16 17 public static void main(String[] args) { 18 Type3 t3 = new Arcane3(); 19 t3.f(); 20 } 21 }
方法f 在 Type1 接口中聲明要拋出被檢查異
CloneNotSupportedException,而且在Type2 接口中聲明要拋出
被檢查異常InterruptedException。Type3 接口繼承了Type1 和Type2,所以,看起來在靜態類型爲Type3的對象上調用方法f時,有潛在可能會拋出這些異常。一個方法必需要麼捕獲其方法體能夠拋出的全部被檢查異常,要麼聲明它將拋出這些異常。
Arcane3 的main 方法在靜態類型爲Type3 的對象上調用了方法f,但
它對CloneNotSupportedException 和InterruptedExceptioin 並無做這些處理。那麼,爲何這個程序能夠編譯呢?
上述分析的缺陷在於對「Type3.f 能夠拋出在Type1.f 上聲明的異常和在
Type2.f 上聲明的異常」所作的假設。這並不正確,由於每個接口都限制了方
法f 能夠拋出的被檢查異常集合。一個方法能夠拋出的被檢查異常集合是它所適
用的全部類型聲明要拋出的被檢查異常集合的交集,而不是合集。所以,靜態類
型爲Type3 的對象上的f 方法根本就不能拋出任何被檢查異常。所以,Arcane3能夠毫無錯誤地經過編譯,而且打印Hello world。
這三個程序說明了一項基本要求,即對於捕獲被檢查異常的catch 子句,只有在相應的try 子句能夠拋出這些異常時才被容許。第二個程序說明了這項要求不會應用到的冷僻案例。第三個程序說明了多個繼承而來的throws 子句取的是交集,即異常將減小而不是增長。
class UnwelcomeGuest { public static final long GUEST_USER_ID = -1; private static final long USER_ID; static { try { USER_ID = getUserIdFromEnvironment(); } catch (IdUnavailableException e) { USER_ID = GUEST_USER_ID; System.out.println("Logging in as guest"); } } private static long getUserIdFromEnvironment() throws IdUnavailableException { throw new IdUnavailableException(); } public static void main(String[] args) { System.out.println("User ID: " + USER_ID); } } class IdUnavailableException extends Exception { }
程序將嘗試着從其環境中讀取一個用戶ID,若是這種嘗試失敗了,則缺省地認爲它是一個來賓用戶。
該程序看起來很直觀。對getUserIdFromEnvironment 的調用將拋出一個異常,從而使程序將GUEST_USER_ID(-1L)賦值給USER_ID,並打印Loggin in as guest。而後main 方法執行,使程序打印User ID: -1,可是實際上該程序並不能編譯。出現了編譯錯誤:The final field USER_ID may already have been assigned。 USER_ID 域是一個空final(blank final),它是一個在聲明中沒有進行初始化操做的final 域。很明顯,只有在對USER_ID賦值失敗時,纔會在try 語句塊中拋出異常,所以,在catch 語句塊中賦值是至關安全的。無論怎樣執行靜態初始化操做語句塊,只會對USER_ID 賦值一次,這正是空final 所要求的。 爲何編譯器不知道這些呢?要肯定一個程序是否能夠不止一次地對一個空final 進行賦值是一個很困難的問題。事實上,這是不可能的。爲了可以編寫出一個編譯器,語言規範在這一點上採用了保守的方式。在程序中,一個空final 域只有在它是明確未賦過值的地方纔能夠被賦值。由於它是保守的。
public class Test1 { public static void main(String[] args) { try { System.out.println("Hello world"); System.exit(0); } finally { System.out.println("Goodbye world"); } } }
try 語句塊執行它的 println 語句,而且經過調用System.exit 來提早結束執行。在此時,可能認爲控制權會轉交給finally 語句塊。然而,它只打印了Hello world。 不論try語句塊的執行是正常地仍是意外地結束,finally語句塊確實都會執行。 然而在這個程序中,try 語句塊根本就沒有結束其執行過程。System.exit 方法將中止當前線程和全部其餘當場死亡的線程。finally 子句的出現並不能給予線程繼續去執行的特殊權限。 當System.exit 被調用時,虛擬機在關閉前要執行兩項清理工做。首先,它執行全部的關閉掛鉤操做,這些掛鉤已經註冊到了Runtime.addShutdownHook 上。這對於釋放 VM 以外的資源將頗有幫助。務必要爲那些必須在VM 退出以前發生的行爲關閉掛鉤。 總之,System.exit 將當即中止全部的線程,它並不會使finally 語句塊得 到調用,可是它在中止VM 以前會執行關閉掛鉤操做。當VM 被關閉時,請使用關閉掛鉤來終止外部資源。經過調用System.halt 能夠在不執行關閉掛鉤的狀況下中止VM,可是這個方法不多使用。
class Reluctant { private Reluctant internalInstance = new Reluctant(); public Reluctant() throws Exception { throw new Exception("I'm not coming out"); } public static void main(String[] args) { try { Reluctant b = new Reluctant(); System.out.println("Surprise!"); } catch (Exception ex) { System.out.println("I told you so"); } } }
main 方法調用了 Reluctant 構造器,它將拋出一個異常。你可能指望catch 子句可以捕獲這個異常,而且打印 I told you so。湊近仔細看看這個程序就會發現,Reluctant 實例還包含第二個內部實例,它的構造器也會拋出一個異常。不管拋出哪個異常,看起來main 中的catch 子句都應該捕獲它,可是當嘗試着去運行它時,就會發現它壓根沒有去作這類的事情:它拋出了 StackOverflowError 異常,爲何呢?
與大多數拋出 StackOverflowError 異常的程序同樣,本程序也包含了一個無限遞歸。當你調用一個構造器時,實例變量的初始化操做將先於構造器的程序體而運行。在本題中, internalInstance 變量的初始化操做遞歸調用了構造器,而該構造器經過再次調用Reluctant 構造器而初始化該變量本身的 internalInstance 域,如此無限遞歸下去。這些遞歸調用在構造器程序體得到執行機會以前就會拋出StackOverflowError 異常,而它是Error 的子類型而不是Exception 的子類型,因此catch 子句沒法捕獲它。
對於一個對象包含與它本身類型相同的實例的狀況,並很多見。例如,連接節點、樹節點和圖節點都屬於這種狀況。必須很是當心地初始化這樣的包含實例,以免StackOverflowError 異常。
class Reluctant { private static Class<Reluctant> engineClass = Reluctant.class; private Engine engine = (Engine) engineClass.newInstance(); public Reluctant() { } } class Engine extends Reluctant { }
構造器必須聲明其實例初始化操做會拋出的全部被檢查異常!儘管其構造器沒有任何程序體,可是它將拋出兩個被檢查異常,InstantiationException 和IllegalAccessException。它們是Class.Instance 拋出的,該方法是在初始化engine 域的時候被調用的。
總之,實例初始化操做是先於構造器的程序體而運行的。實例初始化操做拋出的任何異常都會傳播給構造器。若是初始化操做拋出的是被檢查異常,那麼構造器必須聲明也會拋出這些異常,可是應該避免這樣作,由於它會形成混亂。最後,對於咱們所設計的類,若是其實例包含一樣屬於這個類的其餘實例,那麼對這種無限遞歸要格外小心。
個人修改:
class Reluctant { private static Class<Reluctant> engineClass = Reluctant.class; private Engine engine = newEngine(); private static Engine newEngine() { try { return (Engine) engineClass.newInstance(); } catch (IllegalAccessException e) { throw new AssertionError(e); } catch (InstantiationException e) { throw new AssertionError(e); } } public Reluctant() { } } class Engine extends Reluctant { }
class Reluctant { static void copy(String src, String dest) throws IOException { InputStream in = null; OutputStream out = null; try { in = new FileInputStream(src); out = new FileOutputStream(dest); byte[] buf = new byte[1024]; int n; while ((n = in.read(buf)) > 0) out.write(buf, 0, n); } finally { if (in != null) in.close(); if (out != null) out.close(); } } }
方法將一個文件拷貝到另外一個文件,而且被設計爲要關閉它所建立的每個流,即便它碰到I/O 錯誤也要如此。
這個程序看起來已經面面俱到了。其流(in 和out)被初始化爲null,而且新的流一旦被建立,它們立刻就被設置爲這些流域的新值。對於這些域所引用的流,若是不爲空,則finally 語句塊會將其關閉。即使在拷貝操做引起了一個IOException 的狀況下,finally 語句塊也會在方法返回以前執行。 問題在finally 語句塊自身中。close 方法也可能會拋出IOException 異常。如 果這正好發生在in.close 被調用之時,那麼這個異常就會阻止out.close 被調 用,從而使輸出流仍保持在開放狀態。 以前說過,不要在finally語句裏執行任何退出語句,包括continue等,對close 的調用可能會致使finally 語句塊意外結束。編譯器並不能發現此問題,由於close 方法拋出的異常與read 和write 拋出的異常類型相同,而其外圍方法(copy)聲明將傳播該異常。 解決方式是將每個close 都包裝在一個嵌套的try 語句塊中。
從5.0 版本開始,能夠利用Closeable 接口: class Reluctant { static void copy(String src, String dest) throws IOException { InputStream in = null; OutputStream out = null; try { in = new FileInputStream(src); out = new FileOutputStream(dest); byte[] buf = new byte[1024]; int n; while ((n = in.read(buf)) > 0) out.write(buf, 0, n); } finally { closeIgnoringException(in); closeIgnoringException(out); } } private static void closeIgnoringException(Closeable c) { if (c != null) { try { c.close(); } catch (IOException ex) { // There is nothing we can do if close fails } } } }
class Reluctant { public static void main(String[] args) { int[][] tests = { { 6, 5, 4, 3, 2, 1 }, { 1, 2 }, { 1, 2, 3 }, { 1, 2, 3, 4 }, { 1 } }; int successCount = 0; try { int i = 0; while (true) { if (thirdElementIsThree(tests[i++])) successCount++; } } catch (ArrayIndexOutOfBoundsException e) { // No more tests to process } System.out.println(successCount); } private static boolean thirdElementIsThree(int[] a) { return a.length >= 3 & a[2] == 3; } }
該程序用thirdElementIsThree 方法測試了tests 數組中的每個元素。若是傳遞給thirdElementIsThree 的參數具備3 個或更多的元素,而且其第三個元素等於3,那麼該方法將返回true。對於tests中的5 個元素來講,有2 個將返回true,所以看起來該程序應該打印2。 若是運行它,就會發現它打印0。 事實上,這個程序犯了兩個錯誤。 第一個錯誤是該程序使用了一種可怕的循環慣用法,該慣用法依賴的是對數組的訪問會拋出異常。這種慣用法不只難以閱讀,並且運行速度還很是地慢。不要使用異常來進行循環控制;應該只爲異常條件而使用異常。應該使用慣用的循環來遍歷或者使用for-each語句。 修改以後仍是有問題。拋出越界異常。 第二個錯誤,若是傳遞給thirdElementIsThree 的參數具備3 個或更多的元素,而且其第三個元素等於3,那麼該方法將返回true。問題是在這些條件不知足時它會作些什麼。若是觀察返回的布爾表達式,就會發現它與大多數 AND 操做不同。這個表達式是a.length >= 3 & a[2] == 3。一般,在這種狀況下看到的是 && 操做符,而這個表達式使用的是 & 操做符。& 操做符有其餘的含義。除了常見的被看成整型操做數的位AND 操做以外,當被用於布爾操做數時,它的功能被重載爲邏輯AND 操做符。這個操做符與更常常被使用的條件AND 操做符有所不一樣,& 操做符老是要計算它的兩個操做數,而 && 操做符在其左邊的操做數被計算爲false 時,就再也不計算右邊的操做數。所以,thirdElementIsThree 方法老是要試圖訪問其數組參數的第三個元素,即便該數組參數的元素不足3 個也是如此。 修改 & 操做符替換爲 && 操做符便可。經過這樣的修改,這個程序就能夠打印出咱們所指望的2 了。
總之,儘可能不要去用異常終止循環,由於這種用法很是不清晰,並且會掩蓋bug。要意識到邏輯 AND 和 OR 操做符的存在,而且不要因無心識的誤用而受害。
class Strange1 { public static void main(String[] args) { try { Missing m = new Missing(); } catch (java.lang.NoClassDefFoundError ex) { System.out.println("Got it!"); } } } class Strange2 { public static void main(String[] args) { Missing m; try { m = new Missing(); } catch (java.lang.NoClassDefFoundError ex) { System.out.println("Got it!"); } } } class Missing { Missing() { } }
編譯全部這三個類,而後在運行Strange1 和Strange2 以前刪除Missing.class 文件,而後運行前面兩個類,結果分別是什麼?
其中一個拋出了一個未被捕獲的NoClassDefFoundError 異常,而另外一個卻打印出了Got it! 程序Strange1 只在其try 語句塊中說起Missing 類型,所以你可能會認爲它捕獲 NoClassDefFoundError 異常,並打印Got it!另外一方面,程序Strange2 在 try 語句塊以外聲明瞭一個Missing 類型的變量,所以你可能會認爲所產生的NoClassDefFoundError 異常不會被捕獲。 運行這些程序,就會看到它們的行爲正好相反:Strange1拋出了未被捕獲的NoClassDefFoundError異常,Strange2 卻打印出了Got it!要查明爲何會是這樣,咱們須要研究一下由編譯器生成的這些程序的字節碼。
0: new 3: dup 4: invokespecial #3; //Method Missing."<init>":()V 7: astore_1 8: goto 20 11: astore_1 12: getstatic #5; // Field System.out:Ljava/io/PrintStream; 15: ldc #6; // String "Got it!" 17: invokevirtual #7;//Method PrintStream.println: (String); V 20: return Exception table: from to target type 0 8 11 Class java/lang/NoClassDefFoundError
Strange2.main 相對應的字節碼與其只有一條指令不一樣:11: astore_2,這是一條將 catch 語句塊中的捕獲異常存儲到捕獲參數 ex 中的指令。在 Strange1 中,這個參數是存儲在 VM 變量 1 中的,而在Strange2 中,它是存儲在VM 變量 2 中的。這就是兩個類之間惟一的差別,可是它所形成的程序行爲上的差別是很大的!
爲了運行一個程序,VM 要加載和初始化包含main 方法的類。在加載和初始化之間,VM 必須連接(link)類。連接的第一階段是校驗,校驗要確保一個類是完整的,而且遵循語法要求。兩個main都有一個鏈接點,鏈接點是匯聚控制流的,上面main的鏈接點就是指令20.try若是正常結束,就會執行指令8,goto到20,catch語句塊結束也要從指令17走到指令20.問題就出在這裏,如今有兩條路徑達到連接點,因爲兩條路徑中各有一個astore(指令7和指令11),Strange1.main在路徑1(try到return)中,用VM變量1存儲了m,在路徑2(catch到return)中又用VM變量1存儲異常,所以要進行變量類型合併。因此要檢測Missing和NoClassDefFoundError的超類,由於Missing.class已經被刪除了,因此問題出現了,要拋出NoClassDefFoundError異常。由於此時仍是在main執行以前發生的,因此固然沒法捕獲了。這個異常是在校驗期間、在類被初始化以前,而且在main 方法開始執行以前很早就拋出的。這就解釋了爲何沒有打印出任何關於這個未被捕獲異常的跟蹤棧信息。要想編寫一個可以探測出某個類是否丟失的程序,請使用反射來引用類而不要使用一般的語言結構。
class Strange { public static void main(String[] args) throws Exception { try { Object m = Class.forName("Missing").newInstance(); } catch (ClassNotFoundException ex) { System.err.println("Got it!"); } } }
總之,不要對捕獲NoClassDefFoundError 造成依賴。語言規範很是仔細地描述了類初始化是在什麼時候發生的,可是類被加載的時機卻不可預測。更通常地講,捕獲 Error 及其子類型幾乎是徹底不恰當的。這些異常是爲那些不能被恢復的錯誤而保留的。
class Workout { public static void main(String[] args) { workHard(); System.out.println("It's nap time."); } private static void workHard() { try { workHard(); } finally { workHard(); } } }
要不是有try-finally 語句,該程序的行爲將很是明顯:workHard 方法遞歸地調用它自身,直到程序拋出StackOverflowError,在此刻它以這個未捕獲的異常而終止。可是,try-finally 語句把事情搞得複雜了。當它試圖拋出StackOverflowError 時,程序將會在finally 語句塊的workHard 方法中終止,這樣,它就遞歸調用了本身。這看起來確實就像是一個無限循環的祕方,可是這個程序真的會無限循環下去嗎?若是運行它,它彷佛確實是這麼作的,假設棧的深度爲3,這比它實際的深度要小得多。如今讓咱們來跟蹤其執行過程。main 方法調用workHard,而它又從其try 語句塊中遞歸地調用了本身,而後它再一次從其try 語句塊中調用了本身。在此時,棧的深度是3。當workHard 方法試圖從其try 語句塊中再次調用本身時,該調用當即就會以StackOverflowError 而失敗。這個錯誤是在最內部的finally 語句塊中被捕獲的,在此處棧的深度已經達到了3。在那裏,workHard 方法試圖遞歸地調用它本身,可是該調用卻以StackOverflowError 而失敗。這個錯誤將在上一級的finally 語句塊中被捕獲,在此處站的深度是2。該finally 中的調用將與相對應的try 語句塊具備相同的行爲:最終都會產生一個StackOverflowError。
當棧深度爲2的時候,WorkOut 的運行過程如圖所示。在這張圖中,對workHard 的調用用箭頭表示,workHard 的執行用圓圈表示,try 語句塊中的調用用向左邊的向下箭頭表示,finally 語句塊中的調用用向右邊的向下箭頭表示。箭頭上的數字描述了調用的順序。這張圖展現了一個深度爲0 的調用(即main 中的調用),兩個深度爲1 的調用,四個深度爲2 的調用,總共是7 個調用。那4個深度爲2的調用每個都會當即產生StackOverflowError。至少在把棧的深度限制爲2 的VM 上,該程序不會是一個無限循環:它在7 個調用和4 個異常以後就會終止。可是對於真實的VM 又會怎樣呢?它仍然不會是一個無限循環。其調用圖與前面的圖類似,只不過要大得多得多而已。那麼,究竟大到什麼程度呢?
有一個快速的試驗代表許多 VM 都將棧的深度限制爲1024,所以,調用的數量就是1+2+4+8…+2^1024,而拋出的異常的數量是2^1024。假設咱們的機器能夠在每秒鐘內執行1010 個調用,併產生1010個異常,按照當前的標準,這個假設的數量已經至關高了。在這樣的假設條件下,程序將在大約1.7×10^291 年後終止,而太陽的生命週期大約是1010 年,能夠很肯定,沒有人可以看到這個程序終止的時刻。儘管它不是一個無限循環,可是它也就算是一個無限循環吧。
從技術角度講,調用圖是一棵徹底二叉樹,它的深度就是VM 的棧深度的上限。WorkOut 程序的執行過程等因而在先序遍歷這棵樹。在先序遍歷中,程序先訪問一個節點,而後遞歸地訪問它的左子樹和右子樹。對於樹中的每一條邊,都會產生一個調用,而對於樹中的每個節點,都會拋出一個異常。它證實了指數算法對於除了最小輸入以外的全部狀況都是不可行的,它還代表了使用遞歸能夠編寫出一個指數算法,可是不推薦。
因爲arr[0] =0,因此在進入 test()方法裏面會在第一個if 上拋出一個 NullPointerException,接着會執行 finally 的語句, (finally語句先於 throw 語句執行),輸出一個 e,而後回到 main方法中,因爲捕捉到異常,因此進入到主調方法的catch語句中,而後打印一個 E,拋出異常後,程序中止運行。因此最終結果爲 eE
Java語言中的異常處理包括聲明異常、拋出異常、捕獲異常和處理異常四個環節。
throw用於拋出異常。throws關鍵字能夠在方法上聲明該方法要拋出的異常,而後在方法內部經過throw拋出異常對象。
try是用於檢測被包住的語句塊是否出現異常,若是有異常,則拋出異常,並執行catch語句。
cacth用於捕獲從try中拋出的異常並做出處理。
finally語句塊是無論有沒有出現異常都要執行的內容。
異常表示程序運行過程當中可能出現的非正常狀態,運行時異常(也叫非檢查異常)表示虛擬機的一般操做中可能遇到的異常,是一種常見運行錯誤。javac要求方法必須聲明,拋出可能發生的非運行時異常,可是並不要求必須聲明拋出未被捕獲的運行時異常。
Error指程序自己不能恢復和克服的一種嚴重問題。好比說內存溢出,線程死鎖。不可能期望程序能處理這樣的狀況。Exception指軟件自己設計的問題(系統異常也叫運行時,使用者沒法克服)或運行環境變化致使的問題(普通異常也叫檢查異常,使用者能夠克服),程序自己能恢復和克服。也就是說它表示若是程序運行正常,從不會發生的狀況。
異常是指java程序運行時(非編譯)所發生的非正常狀況或錯誤, Java使用面向對象的方式來處理異常,它把程序中發生的每一個異常也都分別封裝到一個對象來表示,該對象中包含有異常的信息。
虛擬機必須宕機的錯誤屬於error,程序能夠死掉也能夠不死掉的錯誤屬於系統異常,程序不該該死掉的錯誤屬於普通異常;
Java對異常進行了分類,不一樣類型的異常分別用不一樣的Java類表示,全部異常的根類爲java.lang.Throwable,下面又派生了兩個子類:Error和Exception;Error表示應用程序自己沒法克服和恢復的一種嚴重問題,程序只有死的份了,例如,說內存溢出和線程死鎖等系統問題。Exception表示程序還可以克服和恢復的問題,其中又分爲系統異常(運行時異常,非檢查)和普通異常(檢查);
系統異常是軟件自己缺陷所致使的問題,也就是軟件開發人員考慮不周所致使的問題,軟件使用者沒法克服和恢復這種問題,但在這種問題下還可讓軟件系統繼續運行或者讓軟件死掉,例如,數組腳本越界(ArrayIndexOutOfBoundsException),空指針異常(NullPointerException)、類轉換異常(ClassCastException);
普通異常是運行環境的變化或異常所致使的問題,是用戶可以克服的問題,例如,網絡斷線,硬盤空間不夠,IO異常,以及SQL異常發生這樣的異常後,程序不該該死掉。
java爲系統異常和普通異常提供了不一樣的解決方案,編譯器強制普通異常必須try..catch處理或用throws聲明繼續拋給上層調用方法處理,因此普通異常也稱爲checked異常,checked 異常也就是咱們常常遇到的IO異常,以及SQL異常都是這種異常。對於這種異常,JAVA編譯器強制要求咱們必需對出現的這些異常進行catch。由於普通異常和運行環境有關係,具客觀性,不可決定性和不可預測性!必須捕獲或者拋給上層。而系統異常(也叫rutime exception)能夠處理也能夠不處理,因此編譯器不強制用try..catch處理或用throws聲明,因此係統異常也稱爲unchecked異常。
這道題主要看代碼量到底多大,若是你長期寫代碼的,應該常常都看到過一些系統方面的異常,不必定真要回答出5個具體的系統異常,但要可以說出什麼是系統異常,以及幾個系統異常就能夠了,固然,這些異常徹底用其英文名稱來寫是最好的!所謂系統異常(不強制處理),都是RuntimeException的子類,在jdk doc中查RuntimeException類,就能夠看到其全部的子類列表,也就是看到了全部的系統異常。我比較有印象的系統異常有:NullPointerException、ArrayIndexOutOfBoundsException、ClassCastException。