讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議113:不要在finally塊中處理返回值 >

建議113:不要在finally塊中處理返回值

在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語句。