引言
上一篇文章,我们学习了java中异常的分类和编译时检查,编译时检查由编译器在编译期间执行,针对的是受检异常。在程序运行期间,java有自己的异常处理机制,这篇文章我们就来重点看一下运行时的异常处理。
首先声明,这里主要根据《java语言规范》第11章中的内容进行讲解,读者可以参考这些章节。
异常的运行时处理的含义
在程序运行过程中,如果某个语句抛出了异常,java就会启动相应的机制来处理发生的异常。我们在编码过程中,可以使用try catch finally来控制异常的处理。下面,我们就来看一下在不同的组合(try-catch,try-catch-finally,try-finally)中,异常的处理过程是怎样的。
try语句
try语句会执行一个语句块。如果抛出了值并且try语句有一个或者多个可以捕获它的catch子句,那么控制流就会转移到第一个这种catch子句上。如果try语句有finally子句,那么将会执行另一个代码块,无论try块是正常结束还是猝然结束,并且无论控制流之前是否转移到某个catch子句上。
try-catch语句
不带finally的try语句是由先执行try块而执行的。然后有以下选择:
- 如果try块的执行正常结束,那么就不会有任何更近一步的动作,并且该try语句正常结束。
- 如果try块的执行因一个值V的throw对象而猝然结束,那么有以下选择:
- 如果V的运行时类型与try语句的任何catch子句的可捕获异常类是赋值兼容的,那么第一个的这种catch子句将被选中。值V被赋值给选中的catch子句的参数,并且该catch子句的语句块将被执行,然后有以下两种情况:
- 如果该块正常结束,那么该try语句正常结束。
- 如果该块因某项原因而猝然结束,那么该try语句会以同样的原因而猝然结束。
- 如果v的运行时类型与try语句的任何catch子句的可捕获异常类都不是赋值兼容的,那么该try语句就会因一个V值的throw对象而猝然结束。
- 如果V的运行时类型与try语句的任何catch子句的可捕获异常类是赋值兼容的,那么第一个的这种catch子句将被选中。值V被赋值给选中的catch子句的参数,并且该catch子句的语句块将被执行,然后有以下两种情况:
- 如果try块的执行因某项原因而猝然结束,那么该try语句就会以同样的原因而猝然结束。
try块中的语句正常结束。
public static void main(String[] args) {
int i =10;
try {
i = i + 100;
System.out.println("在try块内执行了加法操作后i="+i);
}catch (Exception e){
System.out.println("在catch块内发生了异常");
}
System.out.println("在try-catch块之外的语句被执行了");
}
输出如下:
在try块内执行了加法操作后i=110
在try-catch块之外的语句被执行了
在这个示例里面,我们使用了try-catch语句,并且try块中的语句正常运行,没有猝然结束,所以catch块内的语句不会被执行,程序继续运行到try-catch语句范围外。
try块中抛出异常并在catch块中被捕获
public static void main(String[] args) {
int i = 10;
try {
i = i + 100;
throw new RuntimeException();
}catch (Exception e){
System.out.println("catch块中捕获到了异常");
}
System.out.println("在try-catch块之外的语句被执行了");
}
输出如下:
catch块中捕获到了异常
在try-catch块之外的语句被执行了
异常在catch块中被正常处理,并且catch块正常结束,这样会继续运行try-catch块外的程序。
try块中抛出异常并且有多个catch块
public static void main(String[] args) {
int i = 10;
try {
i = i + 100;
throw new RuntimeException();
}catch (IllegalArgumentException e){
System.out.println("IllegalArgumentException块中捕获到了异常");
}catch (RuntimeException e){
System.out.println("RuntimeException中捕获到了异常");
}
System.out.println("在try-catch块之外的语句被执行了");
}
输出结果如下:
RuntimeException中捕获到了异常
在try-catch块之外的语句被执行了
这两个catch子句中,一个捕获的是IllegalArgumentException,第二个捕获的是RuntimeException,而try块中抛出的是RuntimeException,所以会被后者捕获。程序同样会运行到try-catch外的范围。
所有的catch块都没有捕获该异常
public static void main(String[] args) {
int i = 10;
try {
i = i + 100;
throw new IllegalStateException();
}catch (IllegalArgumentException e){
i += 200;
System.out.println("IllegalArgumentException块中捕获到了异常");
}catch (IllegalReceiveException e){
i += 300;
System.out.println("IllegalReceiveException中捕获到了异常");
}
System.out.println(i);
System.out.println("在try-catch块之外的语句被执行了");
}
输出结果如下:
Exception in thread "main" java.lang.IllegalStateException
at person.andy.concurrency.exception.TryCatchTest.main(TryCatchTest.java:11)
try块中抛出的是IllegalStateException,但是两个catch语句中分别捕获的是IllegalArgumentException和IllegalReceiveException异常,都不是IllegalStateException的超类,所以异常没有被捕获,导致try语句猝然结束。try-catch范围外的程序不会被执行。
捕获异常的try块中抛出了异常
public static void main(String[] args) {
int i = 10;
try {
i = i + 100;
throw new IllegalStateException();
}catch (IllegalStateException e){
i += 200;
System.out.println("IllegalArgumentException块中捕获到了异常");
throw new IllegalArgumentException();
}
}
这个例子中,try块中抛出的IllegalStateException异常被catch块捕获,但是catch块紧接着又把这个异常抛出去了,那同样会导致try语句的猝然结束,try-catch范围外的程序不会被执行。
try-finally语句和try-catch-finally语句
带有finally块的try语句是由先执行try块而执行的。然后有以下选择:
- 如果try块的执行正常结束,那么finally块就会被执行:
- 如果finally块正常结束,那么try语句也正常结束。
- 如果finally块因原因S猝然结束,那么该try语句也会因S而猝然结束。
- 如果try块的执行因一个V值的throw对象而猝然结束:
- 如果V的运行时类型与try语句的任何catch子句的可捕获异常类是赋值兼容的,那么第一个(最左边)的这种catch子句将被选中。值V被赋值给选中的catch子句的参数,并且该catch子句的语句块将被执行,然后有以下选择:
- 如果catch块正常结束,那么finally块就会被执行。然后有以下两种情况:
- 如果finally块正常结束,那么try语句也正常结束。
- 如果finally块因某项原因猝然结束,那么该try语句也会以同样的原因而猝然结束。
- 如果catch块因原因R猝然结束,那么finally块就会被执行。然后有以下两种情况:
- 如果finally块正常结束,那么该try语句与就会以原因R而猝然结束。
- 如果finally块因原因S猝然结束,那么该try语句就会因原因S而猝然结束(并且原因R会被舍弃)。
- 如果catch块正常结束,那么finally块就会被执行。然后有以下两种情况:
- 如果V的运行时类型与try语句的任何catch子句的可捕获异常类都不是赋值兼容的,那么finally块就会被执行。然后有以下选择:
- 如果finally块正常结束,那么该try语句就会因一个V值的throw对象而猝然结束。
- 如果finally块印原因S猝然结束,那么该try语句就会因原因S而猝然结束(并且V值的throw对象会被丢弃和忘记)。
- 如果V的运行时类型与try语句的任何catch子句的可捕获异常类是赋值兼容的,那么第一个(最左边)的这种catch子句将被选中。值V被赋值给选中的catch子句的参数,并且该catch子句的语句块将被执行,然后有以下选择:
- 如果try的执行因为其他原因R而猝然结束,那么finally块将被执行:
- 如果finally块正常结束,那么该try语句就会因原因R而猝然结束。
- 如果finally块因原因S猝然结束,那么该try语句就会因原因S而猝然结束(并且原因R会被丢弃)。
try正常结束,finally正常结束
public static void main(String[] args) {
int i = 9;
try {
i = i + 10;
}catch (Exception e){
System.out.println("catch块中捕获到了异常");
}finally {
System.out.println("finally被执行");
}
System.out.println("try-catch-finally外的代码被执行");
}
输出如下:
finally被执行
try-catch-finally外的代码被执行
try正常结束,finally猝然结束
可以看到finally中显式抛出了一个RuntimeException,try-catch-finally外的代码被idea标识为不可达的语句,也就是说,try正常结束,finally猝然结束的情况下,try-catch-finally外的代码不必被执行。
try猝然结束,catch成功捕获并正常结束,finally正常结束
public static void main(String[] args) {
int i = 9;
try {
i = i + 10;
throw new RuntimeException();
}catch (RuntimeException e){
System.out.println("catch块中捕获到了异常");
}finally {
System.out.println("finally被执行");
}
System.out.println("try-catch-finally范围外的代码被执行");
}
输出结果如下:
catch块中捕获到了异常
finally被执行
try-catch-finally范围外的代码被执行
try猝然结束,catch成功捕获并正常结束,finally猝然结束
public static void main(String[] args) {
int i = 9;
try {
i = i + 10;
throw new RuntimeException();
}catch (RuntimeException e){
System.out.println("catch块中捕获到了异常");
}finally {
System.out.println("finally被执行");
int j = 100/0;
}
System.out.println("try-catch-finally范围外的代码被执行");
}
在这段代码里面,try块中抛出了异常并且在catch内被捕获,但是finally块进行了除以0的操作,会抛出异常,输出结果如下:
catch块中捕获到了异常
finally被执行
Exception in thread "main" java.lang.ArithmeticException: / by zero
at person.andy.concurrency.exception.TryCatchFinallyTest.main(TryCatchFinallyTest.java:13)
可以看到finally猝然结束,try-catch-finally范围外的代码不会被执行。
try猝然结束,catch成功捕获但猝然结束,finally正常结束
public static void main(String[] args) {
int i = 9;
try {
i = i + 10;
throw new RuntimeException();
}catch (RuntimeException e){
System.out.println("catch块中捕获到了异常");
int j = 100/0;
}finally {
System.out.println("finally被执行");
}
System.out.println("try-catch-finally范围外的代码被执行");
}
在这个例子中,try中抛出的异常被catch捕获,但是catch中进行了除以0的操作,抛出了异常,转为finally处理,finally正常结束,但是由于catch中发生了异常,就不会执行try-catch-finally外的代码。
输出如下:
catch块中捕获到了异常
finally被执行
Exception in thread "main" java.lang.ArithmeticException: / by zero
at person.andy.concurrency.exception.TryCatchFinallyTest.main(TryCatchFinallyTest.java:11)
try猝然结束,catch成功捕获但猝然结束,finally猝然结束
public static void main(String[] args) {
int i = 9;
try {
i = i + 10;
throw new RuntimeException();
}catch (RuntimeException e){
System.out.println("catch块中捕获到了异常");
int j = 100/0;
}finally {
int k = 100/0;
System.out.println("finally被执行");
}
System.out.println("try-catch-finally范围外的代码被执行");
}
try中抛出了异常,在catch中被捕获,但是catch由于进行了除以0的操作,抛出了异常,转到finally中进行处理,处理过程中同样抛出了异常,try-catch-finally范围外的代码不会被执行。
输出结果如下:
catch块中捕获到了异常
Exception in thread "main" java.lang.ArithmeticException: / by zero
at person.andy.concurrency.exception.TryCatchFinallyTest.main(TryCatchFinallyTest.java:13)
try猝然结束,catch未捕获,finally正常结束
public static void main(String[] args) {
int i = 9;
try {
i = i + 10;
throw new RuntimeException();
}catch (IllegalArgumentException e){
System.out.println("catch块中捕获到了异常");
int j = 100/0;
}finally {
System.out.println("finally被执行");
}
System.out.println("try-catch-finally范围外的代码被执行");
}
这个例子中,try块中抛出了RuntimeException异常,catch语句中捕获的是IllegalArgumentException,因此没有捕获到,转到finally执行,finally正常结束,但是由于try中的异常,导致try-catch-finally外的代码不会执行。
输出结果如下:
finally被执行
Exception in thread "main" java.lang.RuntimeException
at person.andy.concurrency.exception.TryCatchFinallyTest.main(TryCatchFinallyTest.java:8)
try猝然结束,catch未捕获,finally猝然结束
public static void main(String[] args) {
int i = 9;
try {
i = i + 10;
throw new RuntimeException();
}catch (IllegalArgumentException e){
System.out.println("catch块中捕获到了异常");
}finally {
System.out.println("finally被执行");
int j = 100/0;
}
System.out.println("try-catch-finally范围外的代码被执行");
}
在这个例子中,try中抛出了RuntimeException异常,catch语句中捕获的是IllegalArgumentException异常,因此没有捕获到,转到finally运行,finally进行了除以0的操作抛出了异常,try-catch-finally外的代码不会被运行。
finally被执行
Exception in thread "main" java.lang.ArithmeticException: / by zero
at person.andy.concurrency.exception.TryCatchFinallyTest.main(TryCatchFinallyTest.java:13)
带有return的情况
try中带有return
首先看下面的例子:
private static int tryReturn(){
int i = 1;
try{
i ++ ;
System.out.println("try:" + i);
return i;
}catch (Exception e){
i ++;
System.out.println("catch:" + i);
}finally {
i ++;
System.out.println("finally:" + i);
}
return i;
}
public static void main(String[] args) {
System.out.println(tryReturn());
}
代码在try块中没有异常,通过return来返回结果,但是由于finally是必须执行的,那么这时候的执行顺序是怎样的呢?首先看输出的结果:
try:2
finally:3
2
首先解释一下:当try中带有return时,会先执行return前的代码,然后暂时保存需要return的信息,再执行finally中的代码,然后再通过return返回之前保存的信息。这里方法返回的值是try中计算之后的2而不是finally中计算后的3。但是有一点需要注意,看下面的例子:
private static InnerClass tryReturn(){
InnerClass innerClass = new InnerClass();
try{
innerClass.name = "try";
return innerClass;
}catch (Exception e){
innerClass.name = "catch";
}finally {
innerClass.name = "finally";
}
return innerClass;
}
public static void main(String[] args) {
System.out.println(tryReturn().name);
}
private static class InnerClass{
public String name;
}
这次输出的结果是:finally。
这是因为InnerClass是引用类型而不是简单类型,返回的使指向堆上对象实例的引用。而堆上的实例指向的name已经被修改。所以最后输出的是finally。
catch中带有return
private static int catchReturn(){
int i = 1;
try {
i++;
System.out.println("try:"+i);
i = i/0;
}catch (Exception e){
i ++;
System.out.println("catch:"+i);
return i;
}finally {
i ++;
System.out.println("finally:"+i);
}
return i;
}
public static void main(String[] args) {
System.out.println(catchReturn());
}
输出结果是:
try:2
catch:3
finally:4
3
catch中的return和try中一样,会先执行return前的代码,然后暂时保存需要return的信息。所以,这里返回的是try、catch中累计计算后的3,而非finally中计算后的4。
finally中带有return
private static int finallyReturn() {
int i = 1;
try {
i++;
System.out.println("try:" + i);
return i;
} catch (Exception e) {
i++;
System.out.println("catch:" + i);
return i;
} finally {
i++;
System.out.println("finally:" + i);
return i;
}
}
public static void main(String[] args) {
System.out.println(finallyReturn());
}
输出结果为:
try:2
finally:3
3
当finally中有return的时候,try中的return会失效,在执行完finally的return之后,就不会再执行try中的return。这种写法,编译是可以通过的,但是编译器会给出警告,所以不推荐finally中写return。
总结如下:
(1)finally中的代码总会被执行。
(2)当try、catch中有return时,也会执行finally。return的时候,要注意返回值的类型,是否受到finally中代码的影响。
(3)finally中有return时,会直接在finally中退出,导致try、catch中的return失败。
小结
异常的运行时处理可能比较繁琐,涉及到很多种情况并且每种情况下又可能有多种情况。这些不需要死记硬背,我们还是要理解异常处理的基本思想。