謎題1: 優(yōu)柔寡斷
看看下面的程序,它到底打印什么?
1
2
3
4
5
6
7
8
9
10
11
12
|
public class Indecisive { public static void main(String[] args) { System.out.println(decision()); } private static boolean decision() { try { return true ; } finally { return false ; } } } |
運(yùn)行結(jié)果:
false
結(jié)果說(shuō)明:
在一個(gè) try-finally 語(yǔ)句中,finally 語(yǔ)句塊總是在控制權(quán)離開(kāi) try 語(yǔ)句塊時(shí)執(zhí)行的。無(wú)論 try 語(yǔ)句塊是正常結(jié)束的,還是意外結(jié)束的, 情況都是如此。
一條語(yǔ)句或一個(gè)語(yǔ)句塊在它拋出了一個(gè)異常,或者對(duì)某個(gè)封閉型語(yǔ)句執(zhí)行了一個(gè) break 或 continue,或是象這個(gè)程序一樣在方法中執(zhí)行了一個(gè)return 時(shí),將發(fā)生意外結(jié)束。它們之所以被稱為意外結(jié)束,是因?yàn)樗鼈冏柚钩绦蛉グ错樞驁?zhí)行下面的語(yǔ)句。當(dāng) try 語(yǔ)句塊和 finally 語(yǔ)句塊都意外結(jié)束時(shí), try 語(yǔ)句塊中引發(fā)意外結(jié)束的原因?qū)⒈粊G棄, 而整個(gè) try-finally 語(yǔ)句意外結(jié)束的原因?qū)⒂?finally 語(yǔ)句塊意外結(jié)束的原因相同。在這個(gè)程序中,在 try 語(yǔ)句塊中的 return 語(yǔ)句所引發(fā)的意外結(jié)束將被丟棄, try-finally 語(yǔ)句意外結(jié)束是由 finally 語(yǔ)句塊中的 return 而造成的。
簡(jiǎn)單地講, 程序嘗試著 (try) (return) 返回 true, 但是它最終 (finally) 返回(return)的是 false。丟棄意外結(jié)束的原因幾乎永遠(yuǎn)都不是你想要的行為, 因?yàn)橐馔饨Y(jié)束的最初原因可能對(duì)程序的行為來(lái)說(shuō)會(huì)顯得更重要。對(duì)于那些在 try 語(yǔ)句塊中執(zhí)行 break、continue 或 return 語(yǔ)句,只是為了使其行為被 finally 語(yǔ)句塊所否決掉的程序,要理解其行為是特別困難的。總之,每一個(gè) finally 語(yǔ)句塊都應(yīng)該正常結(jié)束,除非拋出的是不受檢查的異常。 千萬(wàn)不要用一個(gè) return、break、continue 或 throw 來(lái)退出一個(gè) finally 語(yǔ)句塊,并且千萬(wàn)不要允許將一個(gè)受檢查的異常傳播到一個(gè) finally 語(yǔ)句塊之外去。對(duì)于語(yǔ)言設(shè)計(jì)者, 也許應(yīng)該要求 finally 語(yǔ)句塊在未出現(xiàn)不受檢查的異常時(shí)必須正常結(jié)束。朝著這個(gè)目標(biāo),try-finally 結(jié)構(gòu)將要求 finally 語(yǔ)句塊可以正常結(jié)束。return、break 或 continue 語(yǔ)句把控制權(quán)傳遞到 finally 語(yǔ)句塊之外應(yīng)該是被禁止的, 任何可以引發(fā)將被檢查異常傳播到 finally 語(yǔ)句塊之外的語(yǔ)句也同樣應(yīng)該是被禁止的。
謎題2: 極端不可思議
下面的三個(gè)程序每一個(gè)都會(huì)打印些什么? 不要假設(shè)它們都可以通過(guò)編譯。
第一個(gè)程序
1
2
3
4
5
6
7
8
9
10
|
import java.io.IOException; public class Arcane1 { public static void main(String[] args) { try { System.out.println( "Hello world" ); } catch (IOException e) { System.out.println( "I've never seen println fail!" ); } } } |
第二個(gè)程序
1
2
3
4
5
6
7
8
9
|
public class Arcane2 { public static void main(String[] args) { try { // If you have nothing nice to say, say nothing } catch (Exception e) { System.out.println( "This can't happen" ); } } } |
第三個(gè)程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
interface Type1 { void f() throws CloneNotSupportedException; } interface Type2 { void f() throws InterruptedException; } interface Type3 extends Type1, Type2 { } public class Arcane3 implements Type3 { public void f() { System.out.println( "Hello world" ); } public static void main(String[] args) { Type3 t3 = new Arcane3(); t3.f(); } } |
運(yùn)行結(jié)果:
(01) 第一個(gè)程序編譯出錯(cuò)!
1
2
3
4
|
Arcane1.java: 9 : exception java.io.IOException is never thrown in body of corresponding try statement } catch (IOException e) { ^ error |
(02) 第二個(gè)程序能正常編譯和運(yùn)行。
(03) 第三個(gè)程序能正常編譯和運(yùn)行。輸出結(jié)果是: Hello world
結(jié)果說(shuō)明:
(01) Arcane1展示了被檢查異常的一個(gè)基本原則。它看起來(lái)應(yīng)該是可以編譯的:try 子句執(zhí)行 I/O,并且 catch 子句捕獲 IOException 異常。但是這個(gè)程序不能編譯,因?yàn)?println 方法沒(méi)有聲明會(huì)拋出任何被檢查異常,而IOException 卻正是一個(gè)被檢查異常。語(yǔ)言規(guī)范中描述道:如果一個(gè) catch 子句要捕獲一個(gè)類型為 E 的被檢查異常, 而其相對(duì)應(yīng)的 try 子句不能拋出 E 的某種子類型的異常,那么這就是一個(gè)編譯期錯(cuò)誤。
(02) 基于同樣的理由,第二個(gè)程序,Arcane2,看起來(lái)應(yīng)該是不可以編譯的,但是它卻可以。它之所以可以編譯,是因?yàn)樗ㄒ坏?catch 子句檢查了 Exception。盡管在這一點(diǎn)上十分含混不清,但是捕獲 Exception 或 Throwble 的 catch 子句是合法的,不管與其相對(duì)應(yīng)的 try 子句的內(nèi)容為何。盡管 Arcane2 是一個(gè)合法的程序,但是 catch 子句的內(nèi)容永遠(yuǎn)的不會(huì)被執(zhí)行,這個(gè)程序什么都不會(huì)打印。
(03) 第三個(gè)程序,Arcane3,看起來(lái)它也不能編譯。方法 f 在 Type1 接口中聲明要拋出被檢查異常 CloneNotSupportedException,并且在 Type2 接口中聲明要拋出被檢查異常 InterruptedException。Type3 接口繼承了 Type1 和 Type2,因此, 看起來(lái)在靜態(tài)類型為 Type3 的對(duì)象上調(diào)用方法 f 時(shí), 有潛在可能會(huì)拋出這些異常。一個(gè)方法必須要么捕獲其方法體可以拋出的所有被檢查異常, 要么聲明它將拋出這些異常。Arcane3 的 main 方法在靜態(tài)類型為 Type3 的對(duì)象上調(diào)用了方法 f,但它對(duì)
CloneNotSupportedException 和 InterruptedExceptioin 并沒(méi)有作這些處理。那么,為什么這個(gè)程序可以編譯呢?
上述分析的缺陷在于對(duì)“Type3.f 可以拋出在 Type1.f 上聲明的異常和在 Type2.f 上聲明的異常”所做的假設(shè)。這并不正確,因?yàn)槊恳粋€(gè)接口都限制了方法 f 可以拋出的被檢查異常集合。一個(gè)方法可以拋出的被檢查異常集合是它所適用的所有類型聲明要拋出的被檢查異常集合的交集,而不是合集。因此,靜態(tài)類型為 Type3 的對(duì)象上的 f 方法根本就不能拋出任何被檢查異常。因此,Arcane3可以毫無(wú)錯(cuò)誤地通過(guò)編譯,并且打印 Hello world。
謎題3: 不受歡迎的賓客
下面的程序會(huì)打印出什么呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public 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 { } |
運(yùn)行結(jié)果:
1
2
3
4
|
UnwelcomeGuest.java: 10 : variable USER_ID might already have been assigned USER_ID = GUEST_USER_ID; ^ error |
結(jié)果說(shuō)明:
該程序看起來(lái)很直觀。對(duì) getUserIdFromEnvironment 的調(diào)用將拋出一個(gè)異常, 從而使程序?qū)?GUEST_USER_ID(-1L)賦值給 USER_ID, 并打印 Loggin in as guest。 然后 main 方法執(zhí)行,使程序打印 User ID: -1。表象再次欺騙了我們,該程序并不能編譯。如果你嘗試著去編譯它, 你將看到和一條錯(cuò)誤信息。
問(wèn)題出在哪里了?USER_ID 域是一個(gè)空 final(blank final),它是一個(gè)在聲明中沒(méi)有進(jìn)行初始化操作的 final 域。很明顯,只有在對(duì) USER_ID 賦值失敗時(shí),才會(huì)在 try 語(yǔ)句塊中拋出異常,因此,在 catch 語(yǔ)句塊中賦值是相 當(dāng)安全的。不管怎樣執(zhí)行靜態(tài)初始化操作語(yǔ)句塊,只會(huì)對(duì) USER_ID 賦值一次,這正是空 final 所要求的。為什么編譯器不知道這些呢? 要確定一個(gè)程序是否可以不止一次地對(duì)一個(gè)空 final 進(jìn)行賦值是一個(gè)很困難的問(wèn)題。事實(shí)上,這是不可能的。這等價(jià)于經(jīng)典的停機(jī)問(wèn)題,它通常被認(rèn)為是不可能解決的。為了能夠編寫出一個(gè)編譯器,語(yǔ)言規(guī)范在這一點(diǎn)上采用了保守的方式。在程序中,一個(gè)空 final 域只有在它是明確未賦過(guò)值的地方才可以被賦值。規(guī)范長(zhǎng)篇大論,對(duì)此術(shù)語(yǔ)提供了一個(gè)準(zhǔn)確的但保守的定義。 因?yàn)樗潜J氐?所以編譯器必須拒絕某些可以證明是安全的程序。這個(gè)謎題就展示了這樣的一個(gè)程序。幸運(yùn)的是, 你不必為了編寫 Java 程序而去學(xué)習(xí)那些駭人的用于明確賦值的細(xì)節(jié)。通常明確賦值規(guī)則不會(huì)有任何妨礙。如果碰巧你編寫了一個(gè)真的可能會(huì)對(duì)一個(gè)空f(shuō)inal 賦值超過(guò)一次的程序,編譯器會(huì)幫你指出的。只有在極少的情況下,就像本謎題一樣, 你才會(huì)編寫出一個(gè)安全的程序, 但是它并不滿足規(guī)范的形式化要求。編譯器的抱怨就好像是你編寫了一個(gè)不安全的程序一樣,而且你必須修改你的程序以滿足它。
解決這類問(wèn)題的最好方式就是將這個(gè)煩人的域從空 final 類型改變?yōu)槠胀ǖ膄inal 類型,用一個(gè)靜態(tài)域的初始化操作替換掉靜態(tài)的初始化語(yǔ)句塊。實(shí)現(xiàn)這一點(diǎn)的最佳方式是重構(gòu)靜態(tài)語(yǔ)句塊中的代碼為一個(gè)助手方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public class UnwelcomeGuest { public static final long GUEST_USER_ID = - 1 ; private static final long USER_ID = getUserIdOrGuest(); private static long getUserIdOrGuest() { try { return getUserIdFromEnvironment(); } catch (IdUnavailableException e) { System.out.println( "Logging in as guest" ); return GUEST_USER_ID; } } 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 { } |
程序的這個(gè)版本很顯然是正確的,而且比最初的版本根據(jù)可讀性,因?yàn)樗鼮榱擞蛑档挠?jì)算而增加了一個(gè)描述性的名字, 而最初的版本只有一個(gè)匿名的靜態(tài)初始化操作語(yǔ)句塊。將這樣的修改作用于程序,它就可以如我們的期望來(lái)運(yùn)行了。總之,大多數(shù)程序員都不需要學(xué)習(xí)明確賦值規(guī)則的細(xì)節(jié)。該規(guī)則的作為通常都是正確的。如果你必須重構(gòu)一個(gè)程序,以消除由明確賦值規(guī)則所引發(fā)的錯(cuò)誤,那么你應(yīng)該考慮添加一個(gè)新方法。這樣做除了可以解決明確賦值問(wèn)題,還可以使程序的可讀性提高。
謎題4: 您好,再見(jiàn)!
下面的程序?qū)?huì)打印出什么呢?
1
2
3
4
5
6
7
8
9
10
|
public class HelloGoodbye { public static void main(String[] args) { try { System.out.println( "Hello world" ); System.exit( 0 ); } finally { System.out.println( "Goodbye world" ); } } } |
運(yùn)行結(jié)果:
Hello world
結(jié)果說(shuō)明:
這個(gè)程序包含兩個(gè) println 語(yǔ)句: 一個(gè)在 try 語(yǔ)句塊中, 另一個(gè)在相應(yīng)的 finally語(yǔ)句塊中。try 語(yǔ)句塊執(zhí)行它的 println 語(yǔ)句,并且通過(guò)調(diào)用 System.exit 來(lái)提前結(jié)束執(zhí)行。在此時(shí),你可能希望控制權(quán)會(huì)轉(zhuǎn)交給 finally 語(yǔ)句塊。然而,如果你運(yùn)行該程序,就會(huì)發(fā)現(xiàn)它永遠(yuǎn)不會(huì)說(shuō)再見(jiàn):它只打印了 Hello world。這是否違背了"Indecisive示例" 中所解釋的原則呢? 不論 try 語(yǔ)句塊的執(zhí)行是正常地還是意外地結(jié)束, finally 語(yǔ)句塊確實(shí)都會(huì)執(zhí)行。然而在這個(gè)程序中,try 語(yǔ)句塊根本就沒(méi)有結(jié)束其執(zhí)行過(guò)程。System.exit 方法將停止當(dāng)前線程和所有其他當(dāng)場(chǎng)死亡的線程。finally 子句的出現(xiàn)并不能給予線程繼續(xù)去執(zhí)行的特殊權(quán)限。
當(dāng) System.exit 被調(diào)用時(shí),虛擬機(jī)在關(guān)閉前要執(zhí)行兩項(xiàng)清理工作。首先,它執(zhí)行所有的關(guān)閉掛鉤操作,這些掛鉤已經(jīng)注冊(cè)到了 Runtime.addShutdownHook 上。這對(duì)于釋放 VM 之外的資源將很有幫助。務(wù)必要為那些必須在 VM 退出之前發(fā)生的行為關(guān)閉掛鉤。下面的程序版本示范了這種技術(shù),它可以如我們所期望地打印出 Hello world 和 Goodbye world:
1
2
3
4
5
6
7
8
9
10
11
12
|
public class HelloGoodbye1 { public static void main(String[] args) { System.out.println( "Hello world" ); Runtime.getRuntime().addShutdownHook( new Thread() { public void run() { System.out.println( "Goodbye world" ); } }); System.exit( 0 ); } } |
VM 執(zhí)行在 System.exit 被調(diào)用時(shí)執(zhí)行的第二個(gè)清理任務(wù)與終結(jié)器有關(guān)。如果System.runFinalizerOnExit 或它的魔鬼雙胞胎 Runtime.runFinalizersOnExit被調(diào)用了,那么 VM 將在所有還未終結(jié)的對(duì)象上面調(diào)用終結(jié)器。這些方法很久以前就已經(jīng)過(guò)時(shí)了,而且其原因也很合理。無(wú)論什么原因,永遠(yuǎn)不要調(diào)用System.runFinalizersOnExit 和 Runtime.runFinalizersOnExit: 它們屬于 Java類庫(kù)中最危險(xiǎn)的方法之一[ThreadStop]。調(diào)用這些方法導(dǎo)致的結(jié)果是,終結(jié)器會(huì)在那些其他線程正在并發(fā)操作的對(duì)象上面運(yùn)行, 從而導(dǎo)致不確定的行為或?qū)е滤梨i。
總之,System.exit 將立即停止所有的程序線程,它并不會(huì)使 finally 語(yǔ)句塊得到調(diào)用,但是它在停止 VM 之前會(huì)執(zhí)行關(guān)閉掛鉤操作。當(dāng) VM 被關(guān)閉時(shí),請(qǐng)使用關(guān)閉掛鉤來(lái)終止外部資源。通過(guò)調(diào)用 System.halt 可以在不執(zhí)行關(guān)閉掛鉤的情況下停止 VM,但是這個(gè)方法很少使用。
謎題5: 不情愿的構(gòu)造器
下面的程序?qū)⒋蛴〕鍪裁茨?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public 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" ); } } } |
運(yùn)行結(jié)果:
1
2
3
|
Exception in thread "main" java.lang.StackOverflowError at Reluctant.<init>(Reluctant.java: 3 ) ... |
結(jié)果說(shuō)明:
main 方法調(diào)用了 Reluctant 構(gòu)造器,它將拋出一個(gè)異常。你可能期望 catch 子句能夠捕獲這個(gè)異常,并且打印 I told you so。湊近仔細(xì)看看這個(gè)程序就會(huì)發(fā)現(xiàn),Reluctant 實(shí)例還包含第二個(gè)內(nèi)部實(shí)例,它的構(gòu)造器也會(huì)拋出一個(gè)異常。無(wú)論拋出哪一個(gè)異常,看起來(lái) main 中的 catch 子句都應(yīng)該捕獲它,因此預(yù)測(cè)該程序?qū)⒋蛴?I told you 應(yīng)該是一個(gè)安全的賭注。但是當(dāng)你嘗試著去運(yùn)行它時(shí),就會(huì)發(fā)現(xiàn)它壓根沒(méi)有去做這類的事情:它拋出了 StackOverflowError 異常,為什么呢?
與大多數(shù)拋出 StackOverflowError 異常的程序一樣,本程序也包含了一個(gè)無(wú)限遞歸。當(dāng)你調(diào)用一個(gè)構(gòu)造器時(shí),實(shí)例變量的初始化操作將先于構(gòu)造器的程序體而運(yùn)行[JLS 12.5]。在本謎題中, internalInstance 變量的初始化操作遞歸調(diào)用了構(gòu)造器,而該構(gòu)造器通過(guò)再次調(diào)用 Reluctant 構(gòu)造器而初始化該變量自己的 internalInstance 域,如此無(wú)限遞歸下去。這些遞歸調(diào)用在構(gòu)造器程序體獲得執(zhí)行機(jī)會(huì)之前就會(huì)拋出 StackOverflowError 異常,因?yàn)?StackOverflowError 是 Error 的子類型而不是 Exception 的子類型,所以 catch 子句無(wú)法捕獲它。對(duì)于一個(gè)對(duì)象包含與它自己類型相同的實(shí)例的情況,并不少見(jiàn)。例如,鏈接列表節(jié)點(diǎn)、樹節(jié)點(diǎn)和圖節(jié)點(diǎn)都屬于這種情況。你必須非常小心地初始化這樣的包含實(shí)例,以避免 StackOverflowError 異常。
至于本謎題名義上的題目:聲明將拋出異常的構(gòu)造器,你需要注意,構(gòu)造器必須聲明其實(shí)例初始化操作會(huì)拋出的所有被檢查異常。
謎題6: 域和流
下面的方法將一個(gè)文件拷貝到另一個(gè)文件,并且被設(shè)計(jì)為要關(guān)閉它所創(chuàng)建的每一個(gè)流,即使它碰到 I/O 錯(cuò)誤也要如此。遺憾的是,它并非總是能夠做到這一點(diǎn)。為什么不能呢,你如何才能訂正它呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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(); } } |
謎題分析:
這個(gè)程序看起來(lái)已經(jīng)面面俱到了。其流域(in 和 out)被初始化為 null,并且新的流一旦被創(chuàng)建,它們馬上就被設(shè)置為這些流域的新值。對(duì)于這些域所引用的流,如果不為空,則 finally 語(yǔ)句塊會(huì)將其關(guān)閉。即便在拷貝操作引發(fā)了一個(gè) IOException 的情況下,finally 語(yǔ)句塊也會(huì)在方法返回之前執(zhí)行。出什么錯(cuò)了呢?
問(wèn)題在 finally 語(yǔ)句塊自身中。close 方法也可能會(huì)拋出 IOException 異常。如果這正好發(fā)生在 in.close 被調(diào)用之時(shí),那么這個(gè)異常就會(huì)阻止 out.close 被調(diào)用,從而使輸出流仍保持在開(kāi)放狀態(tài)。請(qǐng)注意,該程序違反了"優(yōu)柔寡斷" 的建議:對(duì) close 的調(diào)用可能會(huì)導(dǎo)致 finally 語(yǔ)句塊意外結(jié)束。遺憾的是,編譯器并不能幫助你發(fā)現(xiàn)此問(wèn)題,因?yàn)?close 方法拋出的異常與 read 和 write 拋出的異常類型相同,而其外圍方法(copy)聲明將傳播該異常。解決方式是將每一個(gè) close 都包裝在一個(gè)嵌套的 try 語(yǔ)句塊中。
下面的 finally 語(yǔ)句塊的版本可以保證在兩個(gè)流上都會(huì)調(diào)用 close:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
try { // 和之前一樣 } finally { if (in != null ) { try { in.close(); } catch (IOException ex) { // There is nothing we can do if close fails } } if (out != null ) { try { out.close(); } catch (IOException ex) { // There is nothing we can do if close fails } } } |
總之,當(dāng)你在 finally 語(yǔ)句塊中調(diào)用 close 方法時(shí),要用一個(gè)嵌套的 try-catch 語(yǔ)句來(lái)保護(hù)它,以防止 IOException 的傳播。更一般地講,對(duì)于任何在 finally 語(yǔ)句塊中可能會(huì)拋出的被檢查異常都要進(jìn)行處理,而不是任其傳播。
謎題7: 異常為循環(huán)而拋
下面的程序會(huì)打印出什么呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public class Loop { 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 ; } } |
運(yùn)行結(jié)果:
0
結(jié)果說(shuō)明:
該程序主要說(shuō)明了兩個(gè)問(wèn)題。
第1個(gè)問(wèn)題:不應(yīng)該使用異常作為終止循環(huán)的手段!
該程序用 thirdElementIsThree 方法測(cè)試了 tests 數(shù)組中的每一個(gè)元素。遍歷這個(gè)數(shù)組的循環(huán)顯然是非傳統(tǒng)的循環(huán):它不是在循環(huán)變量等于數(shù)組長(zhǎng)度的時(shí)候終止,而是在它試圖訪問(wèn)一個(gè)并不在數(shù)組中的元素時(shí)終止。盡管它是非傳統(tǒng)的,但是這個(gè)循環(huán)應(yīng)該可以工作。
如果傳遞給 thirdElementIsThree 的參數(shù)具有 3 個(gè)或更多的元素,并且其第三個(gè)元素等于 3,那么該方法將返回 true。對(duì)于 tests中的 5 個(gè)元素來(lái)說(shuō),有 2 個(gè)將返回 true,因此看起來(lái)該程序應(yīng)該打印 2。如果你運(yùn)行它,就會(huì)發(fā)現(xiàn)它打印的時(shí) 0。肯定是哪里出了問(wèn)題,你能確定嗎? 事實(shí)上,這個(gè)程序犯了兩個(gè)錯(cuò)誤。第一個(gè)錯(cuò)誤是該程序使用了一種可怕的循環(huán)慣用法,該慣用法依賴的是對(duì)數(shù)組的訪問(wèn)會(huì)拋出異常。這種慣用法不僅難以閱讀, 而且運(yùn)行速度還非常地慢。不要使用異常來(lái)進(jìn)行循環(huán)控制;應(yīng)該只為異常條件而使用異常。為了糾正這個(gè)錯(cuò)誤,可以將整個(gè) try-finally 語(yǔ)句塊替換為循環(huán)遍歷數(shù)組的標(biāo)準(zhǔn)慣用法:
1
2
3
|
for ( int i = 0 ; i < test.length; i++) if (thirdElementIsThree(tests[i])) successCount++; |
如果你使用的是 5.0 或者是更新的版本,那么你可以用 for 循環(huán)結(jié)構(gòu)來(lái)代替:
1
2
3
|
for ( int [] test : tests) if (thirdElementIsThree(test)) successCount++; |
第2個(gè)問(wèn)題: 主要比較"&操作符" 和 "&&操作符"的區(qū)別。注意示例中的操作符是&,這是按位進(jìn)行"與"操作。
以上所述是小編給大家介紹的Java異常的幾個(gè)謎題,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)服務(wù)器之家網(wǎng)站的支持!