引言
上一篇文章,我们学习了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.IllegalStateExceptionat 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 zeroat 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 zeroat 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 zeroat 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.RuntimeExceptionat 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 zeroat 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:2finally:32
首先解释一下:当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:2catch:3finally:43
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:2finally:33
当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失败。
小结
异常的运行时处理可能比较繁琐,涉及到很多种情况并且每种情况下又可能有多种情况。这些不需要死记硬背,我们还是要理解异常处理的基本思想。
