阿里巴巴开发手册中有这么一条:【强制】不要在 finally 块中使用 return
, 在开发过程中发现部分同学对这条规则理解不是很透彻,本文将就 try 、catch、finally 的一些问题,分析一下 try 、catch、finally 的处理流程。
首先看一个例子:
public class TryCatchFinally {
public static String test() {
String t = "";
try {
t = "try";
return t;
} catch (Exception e) {
t = "catch";
return t;
} finally {
t = "finally";
}
}
public static void main(String[] args) {
System.out.print(TryCatchFinally.test()); // print try
}
}
首先程序执行try语句块,把变量 t 赋值为 try,由于没有发现异常,接下来执行 finally 语句块,把变量 t 赋值为"finally",然后return t,则 t 的值是 "finally",最后 t 的值就是 "finally",程序结果应该显示 "finally",但是实际结果为 "try" 。
为什么会这样,我们不妨先看看这段代码编译出来的class对应的字节码,看虚拟机内部是如何执行的。
首先将类编译成 class
javac -g:vars TryCatchFinally.java
显示目标 class 文件的字节码信息
javap -verbose TryCatchFinally
编译出来的字节码部分,我们只需关注 test 方法,其它先忽略掉。关于jvm虚拟机字节码指令意思,可查阅Java 虚拟机字节码指令表
public static java.lang.String test();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=4, args_size=0
0: ldc #2 // String
2: astore_0
3: ldc #3 // String try
5: astore_0
6: aload_0
7: astore_1
8: ldc #4 // String finally
10: astore_0
11: aload_1
12: areturn
13: astore_1
14: ldc #6 // String catch
16: astore_0
17: aload_0
18: astore_2
19: ldc #4 // String finally
21: astore_0
22: aload_2
23: areturn
24: astore_3
25: ldc #4 // String finally
27: astore_0
28: aload_3
29: athrow
Exception table:
from to target type
3 8 13 Class java/lang/Exception
3 8 24 any
13 19 24 any
LocalVariableTable:
Start Length Slot Name Signature
14 10 1 e Ljava/lang/Exception;
3 27 0 t Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 13
locals = [ class java/lang/String ]
stack = [ class java/lang/Exception ]
frame_type = 74 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
首先看LocalVariableTable信息,这里面定义了两个变量 一个是 t String类型,一个是 e Exception 类型
接下来看Code部分
通过字节码,我们发现,在 try 语句的 return 块中,return 返回的引用变量( t 是引用类型)并不是try语句外定义的引用变量t,而是系统重新定义了一个局部引用 t ’,这个引用指向了引用 t 对应的值,也就是 "try" ,即使在 finally 语句中把引用 t 指向了值 "finally" ,因为 return 的返回引用已经不是 t ,所以引用 t 的对应的值和 try 语句中的返回值无关了。
下面再看一个例子:
public class TryCatchFinally {
public static String test() {
String t = "";
try {
t = "try";
return t;
} catch (Exception e) {
t = "catch";
return t;
} finally {
t = "finally";
return t;
}
}
public static void main(String[] args) {
System.out.print(TryCatchFinally.test()); // print finally
}
}
这里稍微修改了 第一段代码,只是在 finally 语句块里面加入了 一个 return t 的表达式。
按照第一段代码的解释,先进行try{}语句,然后在 return 之前把当前的t的值 try 保存到一个变量 t',然后执行 finally 语句块,修改了变量 t 的值,在返回变量 t。
这里面有两个return语句,但是程序到底返回的是 "try" 还是 "finally" 。接下来我们还是看字节码信息
public static java.lang.String test();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=4, args_size=0
0: ldc #2 // String
2: astore_0
3: ldc #3 // String try
5: astore_0
6: aload_0
7: astore_1
8: ldc #4 // String finally
10: astore_0
11: aload_0
12: areturn
13: astore_1
14: ldc #6 // String catch
16: astore_0
17: aload_0
18: astore_2
19: ldc #4 // String finally
21: astore_0
22: aload_0
23: areturn
24: astore_3
25: ldc #4 // String finally
27: astore_0
28: aload_0
29: areturn
Exception table:
from to target type
3 8 13 Class java/lang/Exception
3 8 24 any
13 19 24 any
LocalVariableTable:
Start Length Slot Name Signature
14 10 1 e Ljava/lang/Exception;
3 27 0 t Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 13
locals = [ class java/lang/String ]
stack = [ class java/lang/Exception ]
frame_type = 74 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
继续看code属性
前10行第一段代码逻辑一样,重点看11行
11行执行 finally 里面的赋值语句,把变量 t 赋值为 "finally",然后返回t对应的值
我们发现try语句中的 return 语句给忽略。可能 jvm 认为一个方法里面有两个 return 语句并没有太大的意义,所以 try 中的 return 语句给忽略了,直接起作用的是 finally 中的 return 语句,所以这次返回的是 "finally"。
再看看复杂一点的例子:
public class TryCatchFinally {
public static String test() {
String t = "";
try {
t = "try";
Integer.parseInt(null);
return t;
} catch (Exception e) {
t = "catch";
return t;
} finally {
t = "finally";
}
}
public static void main(String[] args) {
System.out.print(TryCatchFinally.test()); // print catch
}
}
这里面 try 语句里面会抛出 java.lang.NumberFormatException
,所以程序会先执行 catch 语句中的逻辑,t 赋值为 catch ,在执行return 之前,会把返回值保存到一个临时变量里面 t ',执行 finally 的逻辑,t 赋值为 "finally",但是会返回 t',所以变量 t 的值和返回值已经没有关系了,返回的是 "catch"
public class TryCatchFinally {
public static String test() {
String t = "";
try {
t = "try";
Integer.parseInt(null);
return t;
} catch (Exception e) {
t = "catch";
return t;
} finally {
t = "finally";
return t;
}
}
public static void main(String[] args) {
System.out.print(TryCatchFinally.test()); // print finally
}
}
这个和例 2 有点类似,由于 try 语句里面抛出异常,程序转入 catch 语句块,catch 语句在执行 return 语句之前执行 finally ,而 finally语句有 return ,则直接执行 finally 的语句值,返回 "finally".
public class TryCatchFinally {
public static String test() {
String t = "";
try {
t = "try";
Integer.parseInt(null);
return t;
} catch (Exception e) {
t = "catch";
Integer.parseInt(null);
return t;
} finally {
t = "finally";
}
}
public static void main(String[] args) {
System.out.print(TryCatchFinally.test());// throw java.lang.NumberFormatException
}
}
这个例子在catch语句块添加了Integer.parser(null)
语句,强制抛出了一个异常。然后 finally 语句块里面没有 return 语句。
继续分析一下,由于 try 语句抛出异常,程序进入 catch 语句块,catch 语句块又抛出一个异常,说明 catch 语句要退出,则执行 finally语句块,对 t 进行赋值。然后 catch 语句块里面抛出异常。
结果是抛出java.lang.NumberFormatException
异常
public class TryCatchFinally {
public static String test() {
String t = "";
try {
t = "try";
Integer.parseInt(null);
return t;
} catch (Exception e) {
t = "catch";
Integer.parseInt(null);
return t;
} finally {
t = "finally";
return t;
}
}
public static void main(String[] args) {
System.out.print(TryCatchFinally.test()); // print finally
}
}
这个例子和上面例子中唯一不同的是,这个例子里面 finally 语句里面有 return 语句块。try catch 中运行的逻辑和上面例子一样,当catch 语句块里面抛出异常之后,进入 finally 语句快,然后返回 t 。则程序忽略 catch 语句块里面抛出的异常信息,直接返回 t 对应的值 也就是 "finally"。方法不会抛出异常
public class TryCatchFinally {
public static String test() {
String t = "";
try {
t = "try";
Integer.parseInt(null);
return t;
} catch (NullPointerException e) {
t = "catch";
return t;
} finally {
t = "finally";
}
}
public static void main(String[] args) {
System.out.print(TryCatchFinally.test()); // throw java.lang.NumberFormatException
}
}
这个例子里面 catch 语句里面 catch 的是 NPE 异常,而不是 java.lang.NumberFormatException
异常,所以不会进入 catch 语句块,直接进入 finally 语句块,finally 对 t 赋值之后,由 try 语句抛出java.lang.NumberFormatException
异常。
public class TryCatchFinally {
public static String test() {
String t = "";
try {
t = "try";
Integer.parseInt(null);
return t;
} catch (NullPointerException e) {
t = "catch";
return t;
} finally {
t = "finally";
return t;
}
}
public static void main(String[] args) {
System.out.print(TryCatchFinally.test()); // print finally
}
}
和上面的例子中 try catch 的逻辑相同,try 语句执行完成执行 finally 语句,finally 对 t 赋值 并且返回 ,最后程序结果返回 "finally"
public class TryCatchFinally {
public static String test() {
String t = "";
try {
t = "try";
return t;
} catch (Exception e) {
t = "catch";
return t;
} finally {
t = "finally";
String.valueOf(null);
return t;
}
}
public static void main(String[] args) {
System.out.print(TryCatchFinally.test());
}
}
这个例子中,对 finally 语句中添加了String.valueOf(null)
, 强制抛出 NPE 异常。首先程序执行 try 语句,再执行finally语句块,finally 语句抛出 NPE 异常,整个结果返回NPE异常。
The END.
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。