本文内容参考网络上多篇文章,在这里进行了归纳和总结,希望对你有所帮助
前言:
java 中的异常处理机制你真的理解了吗?掌握了吗?
catch 体里遇到 return 是怎么处理? finally 体遇到 return 怎么办?finally 体里有 System.exit() 方法怎么处理?当 catch 和 finally 体里同时遇上 return 怎么办?
相信你在处理异常的时候不是每次都把它 throws 掉就完事了,很多时候异常是需要我们自己来 catch 并针对所抛出的 Exception 做一些后续的处理工作。
Java 异常体系
- Throwable: Java中所有异常和错误类的父类。只有这个类的实例(或者子类的实例)可以被虚拟机抛出或者被java的throw关键字抛出。同样,只有其或其子类可以出现在catch子句里面。
- Error: Throwable的子类,表示严重的问题发生了,而且这种错误是不可恢复的。
- Exception: Throwable的子类,应用程序应该要捕获其或其子类(RuntimeException例外),称为checked exception。比如:IOException, NoSuchMethodException…
- RuntimeException: Exception的子类,运行时异常,程序可以不捕获,称为unchecked exception。比如:NullPointException.
代码如下:
private void xxxFunc() {
if (openTime == 0) {
return;
}
long saveTime = System.currentTimeMillis(); // 保存模板的时间点
try {
tic.xxxF(template, this, openTime, saveTime);
} catch (Throwable th) { // 不管xxx过程中出现任何异常,都不应该影响模版保存
}
openTime = saveTime; // 更新 openTime,准备下一次计算
}
测试代码
直接上代码,先贴下面测试需要调用的方法:
// catch 后续处理工作
public static boolean catchMethod() {
System.out.print("call catchMethod and return --->> ");
return false;
}
// finally后续处理工作
public static void finallyMethod() {
System.out.println();
System.out.print("call finallyMethod and do something --->> ");
}
1. 抛出 Exception,没有 finally,当 catch 遇上 return
public static boolean catchTest() {
try {
int i = 10 / 0; // 抛出 Exception,后续处理被拒绝
System.out.println("i vaule is : " + i);
return true; // Exception 已经抛出,没有获得被执行的机会
} catch (Exception e) {
System.out.println(" -- Exception --");
return catchMethod(); // Exception 抛出,获得了调用方法并返回方法值的机会
}
}
后台输出结果:
-- Exception --
call catchMethod and return --->> false
2. 抛出 Exception,当 catch 体里有 return,finally 体的代码块将在 catch 执行 return 之前被执行
try {
int i = 10 / 0; // 抛出 Exception,后续处理被拒绝
System.out.println("i vaule is : " + i);
return true; // Exception 已经抛出,没有获得被执行的机会
} catch (Exception e) {
System.out.println(" -- Exception --");
return catchMethod(); // Exception 抛出,获得了调用方法的机会,但方法值在 finally 执行完后才返回
} finally{
}
后台输出结果:
3. 不抛 Exception,当 finally 代码块里面遇上 return,finally 执行完后将结束整个方法
public static boolean catchFinallyTest2() {
try {
int i = 10 / 2; // 不抛出 Exception
System.out.println("i vaule is : " + i);
return true; // 获得被执行的机会,但执行需要在 finally 执行完成之后才能被执行
} catch (Exception e) {
System.out.println(" -- Exception --");
return catchMethod();
}
后台输出结果:
4. 不抛 Exception,当 finally 代码块里面遇上 System.exit() 方法 将结束和终止整个程序,而不只是方法
public static boolean finallyExitTest() {
try {
int i = 10 / 2; // 不抛出 Exception
System.out.println("i vaule is : " + i);
return true; // 获得被执行的机会,但由于 finally 已经终止程序,返回值没有机会被返回
} catch (Exception e) {
System.out.println(" -- Exception --");
return true;
} finally {
finallyMethod();
System.exit(0); // finally 中含有 System.exit() 语句,System.exit() 将退出整个程序,程序将被终止
}
}
后台输出结果:
i vaule is : 5
call finallyMethod and do something --->>
5. 抛出 Exception,当 catch 和 finally 同时遇上 return,catch 的 return 返回值将不会被返回,finally 的 return 语句将结束整个方法并返回
public static boolean finallyTest1() {
try {
int i = 10 / 0; // 抛出 Exception,后续处理被拒绝
System.out.println("i vaule is : " + i);
return true; // Exception 已经抛出,没有获得被执行的机会
} catch (Exception e) {
System.out.println(" -- Exception --");
return true; // Exception 已经抛出,获得被执行的机会,但返回操作将被 finally 截断
}finally {
return false; // return 将结束整个方法,返回 false
}
后台输出结果:
call finallyMethod and do something --->> false
6. 不抛出 Exception,当 finally 遇上 return,try 的 return 返回值将不会被返回,finally 的 return 语句将结束整个方法并返回
public static boolean finallyTest2() {
try {
int i = 10 / 2; // 不抛出 Exception
System.out.println("i vaule is : " + i);
return true; // 获得被执行的机会,但返回将被 finally 截断
} catch (Exception e) {
System.out.println(" -- Exception --");
return true;
}
后台输出结果:
call finallyMethod and do something --->> false
7. catch里再throw异常,还是要咨询finally里的代码
public static void main(String []args) {
try{
a();
}
catch(Exception e){
System.out.println("exception");
}
System.out.println("finished");
}
public static void a() throws Exception{
try{
throw new Exception();
}
catch(Exception e){
System.out.println("exception000");
throw e;
}
finally{
System.out.println(" finally111");
}
}
}
结果:
exception000
finally111
exception
finished
什么时候finally执行?什么时候不执行?
经过搜索,找到一位前辈的博客,她写到至少两种情况下finally是出现但不执行的
- try语句没有被执行到,如在try语句之前就返回了,这样finally语句就不会执行,这也说明了finally语句被执行的必要而非充分条件是:相应的try语句一定被执行到。
- 在try块中有System.exit(0); 这样的语句,System.exit(0);是终止Java虚拟机JVM的,连JVM都停止了,所有都结束了,当然finally语句也不会被执行到。
接下来的内容就比较深入细节了,没有这方面了解的需求可直接略过
现在我们已经知道finally后面的语句执行与try/catch中有无return有关,但finally块本身执行是在return的前面还是后面还是什么时候?
一个我比较认同的结论:
finally块的语句在try或catch中的return语句执行之后返回之前执行且finally里的修改语句可能影响也可能不影响try或catch中 return已经确定的返回值,若finally里也有return语句则覆盖try或catch中的return语句直接返回
要想明白这个问题,需要一些背景知识:
java方法是在栈帧中执行,栈帧是线程私有栈的单位,执行方法的线程会为每一个方法分配一小块栈空间来作为该方法执行时的内存空间,栈帧分为三个区域:
- 操作数栈,用来保存正在执行的表达式中的操作数,数据结构中学习过基于栈的多项式求值算法,操作数栈的作用和这个一样
- 局部变量区,用来保存方法中使用的变量,包括方法参数,方法内部声明的变量,以及方法中使用到的对象的成员变量或类的成员变量(静态变量),最后两种变量会复制到局部变量区,因此在多线程 环境下,这种变量需要根据需要声明为volatile类型
- 字节码指令区,这个不用解释了,就是方法中的代码翻译成的指令
return语句:
return语句的格式如下:
return [expression];
其中expression(表达式)是可选的,因为有些方法没有返回值,所以return后面也就没有表达式,或者可以看做是空的表达式。
我们知道return语句的作用可以结束方法并返回一个值,那么他返回的是哪里的值呢?返回的是return指令执行的时刻,操作数栈顶的值,不管expression是一个怎样的表达式,究竟做了些什么工作,对于return指令来说都不重要,他只负责把操作数栈顶的值返回。
而return expression是分成两部分执行的:
- 执行:expression;
- 执行:return指令;
例如:return x+y;
这句代码先执行x+y,再执行return;首先执行将x以及y从局部变量区复制到操作数栈顶的指令,然后执行加法指令,这个时候结果x+y的值会保存在操作数栈的栈顶,最后执行return指令,返回操作数栈顶的值。
对于return x;先执行x,x也是一个表达式,这个表达式只有一个操作数,会执行将变量x从局部变量区复制到操作数栈顶的指令,然后执行return,返回操作数栈顶的值。
因此return x实际返回的是return指令执行时,x在操作数栈顶的一个快照或者叫副本,而不是x这个值。
finally语句在return语句执行之后还是之前执行?
栗子1:
public class TestFinally {
public static void main(String[] args) {
System.out.println(test1());
}
public static int test1() {
int b = 20;
try {
System.out.println("try");
return b += 80;
}
catch (Exception e) {
System.out.println("catch");
}
finally {
System.out.println("finally");
if (b > 25) {
System.out.println("b>25, b = " + b);
}
}
return b;
}
}
输出:
try
finally
b>25, b = 100
100
说明return语句已经执行了再去执行finally语句,不过并没有直接返回,而是等finally语句执行完了再返回结果。即:finally语句在return语句执行之后return返回之前执行的。
再来一个栗子2:
public class TestFinally {
public static void main(String[] args) {
System.out.println(test11());
}
public static String test11() {
try {
System.out.println("try");
return test12();
} finally {
System.out.println("finally");
}
}
public static String test12() {
System.out.println("return statement");
return "after return";
}
}
输出:
try
return statement
finally
after return
如果finally里也有return语句,那么是不是就直接返回了,try中的return就不能返回了?
public class TestFinally {
public static void main(String[] args) {
System.out.println(test2());
}
public static int test2() {
int b = 20;
try {
System.out.println("try");
return b += 80;
} catch (Exception e) {
System.out.println("catch");
} finally {
System.out.println("finally");
if (b > 25) {
System.out.println("b>25, b = " + b);
}
return 200;
}
// return b;
}
}
输出:
try
finally
b>25, b = 100
200
说明finally里的return直接返回了,就不管try中是否还有返回语句,这里还有个小细节需要注意,finally里加上return过后,finally外面的return b就变成不可到达语句了,也就是永远不能被执行到,所以需要注释掉否则编译器报错。即finally块中的return语句会覆盖try块中的return返回。
如果finally中没有return,会如何返回?
try{
return expression;
}finally{
do some work;
}
首先我们知道,finally语句是一定会执行,但他们的执行顺序是怎么样的呢?他们的执行顺序如下:
1.执行:expression,计算该表达式,结果保存在操作数栈顶;
2.执行:操作数栈顶值(expression的结果)复制到局部变量区作为返回值;
3.执行:finally语句块中的代码;
4.执行:将第2步复制到局部变量区的返回值又复制回操作数栈顶;
5.执行:return指令,返回操作数栈顶的值;
我们可以看到,在第一步执行完毕后,整个方法的返回值就已经确定了,由于还要执行finally代码块,因此程序会将返回值暂存在局部变量区,腾出操作数栈用来执行finally语句块中代码,等finally执行完毕,再将暂存的返回值又复制回操作数栈顶。所以无论finally语句块中(finally内没有retrun语句)执行了什么操作,都无法影响返回值,所以试图在finally语句块中修改返回值是徒劳的。因此,finally语句块设计出来的目的只是为了让方法执行一些重要的收尾工作,而不是用来计算返回值的。
但我前面刚说finally里的return直接返回了,就不管try中是否还有返回语句,都不管try直接返回了,这是不是矛盾呢?
并不是。仔细阅读刚才的代码,可以看到,执行return后面的表达式,但值并没有立即返回,会转去执行finally,然后再返回刚才没有返回的值。如果finally里面返回了一个常值如return 200,这时才会直接返回,不管之前要返回的值。
如果finally里没有return语句,但修改了b的值,那么try中return返回的是修改后的值还是原值?
public class TestFinally {
public static void main(String[] args) {
System.out.println(test3());
}
public static int test3() {
int b = 20;
try {
System.out.println("try");
return b += 80;
} catch (Exception e) {
System.out.println("catch");
} finally {
System.out.println("finally");
if (b > 25) {
System.out.println("b>25, b = " + b);
}
b = 150;
}
return 2000;
}
}
输出:
try
finally
b>25, b = 100
100
如果return使用在基本数据变量上,则finally中对改基本数据变量的修改不会生效(测试发现Integer b = 20;也不行)
!如果作用的是对象是正常对象如Map、List,才生效。
例如:
import java.util.List;
import java.util.LinkedList;
public class HelloWorld {
public static void main(String[] args) {
List<String> appfiles = test3();
System.out.println(appfiles.get(0) +" # "+appfiles.get(1));
}
public static List<String> test3() {
List<String> appfiles = new LinkedList<>();
appfiles.add("11111");
try {
System.out.println("try");
return appfiles;
} catch (Exception e) {
System.out.println("catch");
} finally {
System.out.println("finally");
appfiles.add("2222");
}
return appfiles;
}
}
结果:
try
finally
11111 # 2222
每次返回的一定是try中的return语句?那么finally后面的return语句(如果有)永远不会执行?
try块里的return语句在异常的情况下不会被执行,这样具体返回哪个看情况
public class TestFinally {
public static void main(String[] args) {
System.out.println(test4());
}
public static int test4() {
int b = 20;
try {
System.out.println("try");
b = b / 0;
return b += 80;
} catch (Exception e) {
b += 15;
System.out.println("catch");
} finally {
System.out.println("finally");
if (b > 25) {
System.out.println("b>25, b = " + b);
}
b += 50;
}
return 26;
}
}
输出:
try
catch
finally
b>25, b = 35
26
这里因为在return之前发生了除0异常,所以try中的return不会被执行到,而是接着执行捕获异常的catch 语句和最终的finally语句,此时两者对b的修改都影响了最终的返回值,这时return b;就起到作用了。当然如果将return b改为return 300什么的,最后返回的就是300。
如果catch中有return,则执行情况与未发生异常时try中return的执行情况完全一样。
public class TestFinally {
public static void main(String[] args) {
System.out.println(test5());
}
public static int test5() {
int b = 20;
try {
System.out.println("try");
b = b /0;
return b += 80;
} catch (Exception e) {
System.out.println("catch");
return b += 15;
} finally {
System.out.println("finally");
if (b > 25) {
System.out.println("b>25, b = " + b);
}
b += 50;
}
//return b;
//Unreachable code
}
}
输出:
try
catch
finally
b>25, b = 35
35
说明了发生异常后,catch中的return语句先执行,确定了返回值后再去执行finally块,执行完了catch再返回,finally里对b的改变对返回值无影响,原因同前面一样,也就是说情况与try中的return语句执行完全一样。
如果想输出85,需要这么改,即finally中return,覆盖catch中的try。
public class TestFinally {
public static void main(String[] args) {
System.out.println(test5());
}
public static int test5() {
int b = 20;
try {
System.out.println("try block");
b = b /0;
return b += 80;
} catch (Exception e) {
System.out.println("catch block");
return b += 15;
} finally {
System.out.println("finally block");
if (b > 25) {
System.out.println("b>25, b = " + b);
}
b += 50;
return b;
}
//return b;
}
}
输出:
try block
catch block
finally block
b>25, b = 35
85
至此,此篇结束。关于这块的问题相信我已经明白了二三事,希望看到这的你也有收获 :)
结语:(假设方法需要返回值)
java 的异常处理中:
1、在不抛出异常的情况下,程序执行完 try 里面的代码块之后,该方法并不会立即结束,而是继续试图去寻找该方法有没有 finally 的代码块,
2、如果没有 finally 代码块,整个方法在执行完 try 代码块后返回相应的值来结束整个方法;
3、如果有 finally 代码块,此时程序执行到 try 代码块里的 return 语句之时并不会立即执行 return,而是先去执行 finally 代码块里的代码,
4、若 finally 代码块里没有 return 或没有能够终止程序的代码,程序将在执行完 finally 代码块代码之后再返回 try 代码块执行 return 语句来结束整个方法;
5、若 finally 代码块里有 return 或含有能够终止程序的代码,方法将在执行完 finally 之后被结束,不再跳回 try 代码块执行 return。
6、在抛出异常的情况下,原理也是和上面的一样的,你把上面说到的 try 换成 catch 去理解就 OK 了 *_*
后续参考:
https://blog.csdn.net/S_gy_Zetrov/article/details/68490882