在finally代碼塊中處理返回值,這是考試和面試中經常出現的題目。雖然可以以此來出考試題,但在項目中絕對不能在finally代碼塊中出現return語句,這是因為這種處理方式非常容易產生「誤解」,會嚴重誤導開發者。例如如下代碼:
public static void main(Stringargs){
try{
doStuff(-1);
doStuff(100);
}catch(Exception e){
System.out.println(\"這裡是永遠都不會到達的\");
}
}
//該方法拋出受檢異常
public static int doStuff(int_p)throws Exception{
try{
if(_p<0){
throw new DataFormatException(\"數據格式錯誤\");
}else{
return_p;
}
}catch(Exception e){
//異常處理
throw e;
}finally{
return-1;
}
}
對於這段代碼,有兩個問題:main方法中的doStuff方法的返回值是什麼?doStuff方法永遠都不會拋出異常嗎?
答案是:doStuff(-1)的值是-1,doStuff(100)的值也是-1,調用doStuff方法永遠都不會拋出異常,有這麼神奇?原因就是我們在finally代碼塊中加入了return語句,而這會導致出現以下兩個問題:
(1)覆蓋了try代碼塊中的return返回值
當執行doStuff(-1)時,doStuff方法產生了DataFormatException異常,catch塊在捕捉此異常後直接拋出,之後代碼執行到finally代碼塊,就會重置返回值,結果就是-1了,也就是出現了先返回,再執行finally,再重置返回值的情況。
有讀者可能會擴展思考了:是不是可以定義一個變量,在finally中修改後再return呢?代碼如下:
public static int doStuff(){
int a=1;
try{
return a;
}catch(Exception e){
}finally{
//重新修改一下返回值
a=-1;
}
return 0;
}
該方法的返回值永遠是1,而不會是-1或0(為什麼不會執行到\"return 0\"呢?原因是finally執行完畢後該方法已經有返回值了,後續代碼就不會再執行了),這都是源於異常代碼塊的處理方式,在代碼中加上try代碼塊就標誌著運行時會有一個Throwable線程監視著該方法的運行,若出現異常,則交由異常邏輯處理。
我們知道方法是在棧內存中運行的,並且會按照「先進後出」的原則執行,main方法調用了doStuff方法,則main方法在下層,doStuff在上層,當doStuff方法執行完\"return a\"時,此方法的返回值已經確定是in類型1(a變量的值,注意基本類型都是值拷貝,而不是引用),此後finally代碼塊再修改a的值已經與doStuff返回者沒有任何關係了,因此該方法永遠都會返回1。
繼續追問:那是不是可以在finally代碼塊中修改引用類型的屬性以達到修改返回值的效果呢?代碼如下:
public static Person doStuff(){
Person person=new Person();
person.setName(\"張三\");
try{
return person;
}catch(Exception e){
}finally{
//重新修改一下返回值
person.setName(\"李四\");
}
person.setName(\"王五\");
return person;
}
class Person{
private String name;
/*name的getter/setter方法省略*/
}
此方法的返回值永遠都是name為李四的Person對象,原因是Person是一個引用對象,在try代碼塊中的返回值是Person對象的地址,finally中再修改那當然會是李四了。
(2)屏蔽異常
為什麼明明把異常throw出去了,但main方法卻捕捉不到呢?這是因為異常線程在監視到有異常發生時,就會登記當前的異常類型為DataFormatException,但是當執行器執行finally代碼塊時,則會重新為doStuff方法賦值,也就是告訴調用者「該方法執行正確,沒有產生異常,返回值是1」,於是乎,異常神奇的消失了,其簡化代碼如下所示:
public static void doSomething(){
try{
//正常拋出異常
throw new RuntimeException();
}finally{
//告訴JVM:該方法正常返回
return;
}
}
public static void main(Stringargs){
try{
doSomething();
}catch(RuntimeException e){
System.out.println(\"這裡永遠都不會到達!\");
}
}
上面finally代碼塊中的return已經告訴JVM:doSomething方法正常執行結束,沒有異常,所以main方法就不可能獲得任何異常信息了。這樣的代碼會使可讀性大大降低,讀者很難理解作者的意圖,增加了修改的難度。
在finally中處理return返回值,代碼看上去很完美,都符合邏輯,但是執行起來就會產生邏輯錯誤,最重要的一點是finally是用來做異常的收尾處理的,一旦加上了return語句就會讓程序的複雜度徒然提升,而且會產生一些隱蔽性非常高的錯誤。
與return語句相似,System.exit(0)或Runtime.getRuntime().exit(0)出現在異常代碼塊中也會產生非常多的錯誤假象,增加代碼的複雜性,讀者有興趣可以自行研究一下。
注意 不要在finally代碼塊中出現return語句。