關于try……finally問題中return的問題,看到很多面試機經(jīng)有著不同的說法,這里配合javap對字節(jié)碼進行反匯編之后仔細分析一番责嚷。
這是正常場景,try中return的情況:
public classTest1 {
public static void main(String[] args) {
System.out.println(tryReturn());
}
static String tryReturn() {
String x = "hello";
String y = "你好";
try {
return x;
}
finally {
System.out.println(x);
x = "hi";
System.out.println(x);
}
}
publictestGit.Test1();
Code:
0: aload_0
1: invokespecial #8 // Methodjava/lang/Object."":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #16 // Fieldjava/lang/System.out:Ljava/io/PrintStream;
3: invokestatic #22 // MethodtryReturn:()Ljava/lang/String;
6: invokevirtual #26 // Methodjava/io/PrintStream.println:(Ljava/lang/String;)V
9: return
static java.lang.String tryReturn();
Code:
0: ldc #34 // String hello
2: astore_0
3: ldc #36 // String你好
5: astore_1
6: aload_0
7: astore_3
8: getstatic #16 // Fieldjava/lang/System.out:Ljava/io/PrintStream;
11: aload_0
12: invokevirtual #26 // Methodjava/io/PrintStream.println:(Ljava/lang/String;)V
15:ldc #38 // String hi
17: astore_0
18: getstatic #16 // Fieldjava/lang/System.out:Ljava/io/PrintStream;
21: aload_0
22: invokevirtual #26 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
25: aload_3
26: areturn //關注這個指令碼掂铐,它代表return
27: astore_2
28: getstatic #16 // Fieldjava/lang/System.out:Ljava/io/PrintStream;
31: aload_0
32: invokevirtual #26 // Methodjava/io/PrintStream.println:(Ljava/lang/String;)V
35: ldc #38 // String hi
37: astore_0
38: getstatic #16 // Fieldjava/lang/System.out:Ljava/io/PrintStream;
41: aload_0
42: invokevirtual #26 // Methodjava/io/PrintStream.println:(Ljava/lang/String;)V
45: aload_2
46: athrow
Exception table:
from to target type
6 8 27 any
}
首先明確一點罕拂,即使try中return了,try-catch-finally代碼塊也會執(zhí)行到結(jié)尾全陨,所以finally{}中的代碼是一定會被執(zhí)行的爆班。
然后注意字節(jié)碼指令areturn,它代表return辱姨。我們發(fā)現(xiàn)正常情況下柿菩,要return的對象被裝入了一個新的空間astore_3,同時執(zhí)行返回時也是通過aload_3讀取這個拷貝雨涛。換句話說正常情況下枢舶,finally中的代碼對局部變量的操作不會影響返回值,因為要return的變量是try中變量的副本替久。
注意祟辟,這里也是一個坑所在的地方。變量傳遞的時候都是值傳遞侣肄,所以如果變量存放的是指向?qū)ο蟮膔eference,那么這里就會有個問題醇份,雖然我在finally中修改變量的值不會影響返回的結(jié)果稼锅,但是我在finally中對方法要返回的對象進行操作時可以的。注意這段代碼:
publicclass Test1 {
public static void main(String[] args){
System.out.println(tryReturn().getName());
}
static A tryReturn() {
A a = new A();
try {
a.setName("beijing");
return a;
}
finally {
System.out.println(a.getName());
a.setName("shanghai");
System.out.println(a.getName());//這里成功將a.name變成了上海僚纷,最后主函數(shù)打印時將顯示上海
}
}
}
classA{
private String name;
public void setName(String s) {
name = s;
}
public String getName() {
return name;
}
}
Finally中的代碼成功修改了a中的成員變量矩距,這是要明確注意的。
最后怖竭,我們討論一下finally中也有return的情況:
這是在finally中加入return的情況
publicclassTest1{
publicstaticvoidmain(String[] args) {
System.out.println(tryReturn());
}
staticStringtryReturn() {
Stringx= "hello";
Stringy= "你好";
try{
return x;
}
finally{//這么寫IDE是要給你警告的
System.out.println(x);
x = "hi";
System.out.println(x);
return x;
}
}
}
publicclass testGit.Test1 {
public testGit.Test1();
Code:
0: aload_0
1: invokespecial #8 // Methodjava/lang/Object."":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #16 // Fieldjava/lang/System.out:Ljava/io/PrintStream;
3: invokestatic #22 // MethodtryReturn:()Ljava/lang/String;
6: invokevirtual #26 // Methodjava/io/PrintStream.println:(Ljava/lang/String;)V
9: return
static java.lang.String tryReturn();
Code:
0: ldc #34 // String hello
2: astore_0
3: ldc #36 // String你好
5: astore_1
6: goto 10
9: pop
10: getstatic #16 // Fieldjava/lang/System.out:Ljava/io/PrintStream;
13: aload_0
14: invokevirtual #26 // Methodjava/io/PrintStream.println:(Ljava/lang/String;)V
17: ldc #38 // String hi
19: astore_0
20: getstatic #16 // Fieldjava/lang/System.out:Ljava/io/PrintStream;
23: aload_0
24: invokevirtual #26 // Methodjava/io/PrintStream.println:(Ljava/lang/String;)V
27: aload_0
28: areturn
Exception table:
from to target type
6 9 9 any
}
注意字節(jié)碼锥债,try中的return被跳過去了。根據(jù)底部Exception table的提示痊臭,try中的代碼塊應該是code:6哮肚、9這兩行。然而并沒有執(zhí)行areturn广匙,而是執(zhí)行了goto直接跳到了finally的部分允趟。換句話說如果try和finally中都有return語句,編譯器會忽視try中的return而選擇finally中的return鸦致。當然如果你try和finally都寫return了潮剪,IDE多半是要給你黃線警告的涣楷。
結(jié)論:
try……catch……finally結(jié)構中,代碼一定會按流程執(zhí)行到結(jié)尾抗碰,當然沒有異常被捕捉到的時候catch里的代碼是不執(zhí)行的狮斗。
關于finally在return之前還是之后。細扣的話弧蝇,我認為finally中的代碼生效在return之后碳褒,方法結(jié)束之前。這點從ex1中的字節(jié)碼文件中可以看到捍壤。當然如果將return定義成方法結(jié)束的話骤视,finally也的確就是發(fā)生在return之前的。
關于finally中代碼對返回值的影響鹃觉。簡單地說专酗,如果返回值是基本類型或者string,finally中任何關于局部變量的操作都不會影響到返回值盗扇;如果返回值是在此之外的對象祷肯,請一定注意finally中是可以對對象進行操作的。
關于try和finally中都有return的情況疗隶。一般說來IDE會提醒你不要這么寫(eclipse和IDEA都會給你警告佑笋,當然編譯器是能接受這種寫法的),如果你這么寫了斑鼻,try中的return語句會被編譯器直接跳過蒋纬。