是否需要看这篇文章?

下面的例子中,如果正常执行返回值多少? 如果出现了ArithmeticException返回值多少? 如果出现非ArithmeticException(如NullPointerException)返回值多少?

如果你了解这个例子说明的问题,并了解例子中三种情况下的执行细节,这篇文章你就不用浪费时间看了。

例子:

    public int testException_finally(){
        int x;
        try {
            x = 1;
//int y = 1/0;  //放开此处,出现ArithmeticException。

/*//注释掉 int y = 1/0;处,放开此处,出现NullPointerException
            String str = null;
            str.substring(0);
            */
            return x;
        } catch (ArithmeticException e) {
            x =2;
            return x;
        } finally{
            x = 3;
        }
    }

答案:

如果正常执行,返回值为1;

如果抛出ArithmeticException,返回值为2;

如果抛出其他Exception,则抛出该Exception,无返回值。

一看就知晓的同学们,散了吧。这篇文章你们不需要看了。

不知所云的同学们,请继续往下看。下面要说的正适合你☺

1. Java异常的分类

Throwable类是Java语言中所有异常和错误的基类。Throwable有两个直接子类:Error和Exception。Error用来表示编译期或系统错误,一般不用我们程序员关心(现在还是程序猿,但有一颗想做架构师的心☺);Exception是可以被抛出的异常的基本类型,这个才是我们需要关心的基类。

Java异常在设计时的基本理念是用名称代表发生的问题,异常的名称应该可以望文知意。异常并非全是在java.lang包中定义,像IO异常就定义在java.io包中。在Exception中,有一种异常:RuntimeException(运行时异常)不需要我们在程序中专门捕获,Java会自动捕获此种异常,RuntimeException及其子类异常再加上Error异常,被统一叫做unchecked Exception(非检查异常);其他的Exception及其子类异常(不包括RuntimeException及其子类异常),被统一叫做checked Exception(检查型异常)。对于检查型异常,才是我们需要捕获并处理的异常。非检查型异常Java会自动捕获并抛出。当然,我们也可以主动捕获RuntimeException型异常。但是Error型异常一般不去捕获处理。

2. Java异常处理的基本规则

对于可能发生异常的Java代码块,我们可以将其放入try{}中,然后在try之后使用catch(***Exception e){}处理抛出的具体异常,如果没有匹配的catch处理抛出的异常,则会将该异常向上一层继续抛出,直到抛至Main()方法。

有一些代码,我们希望不管try中的代码是成功还是失败都需要执行,那么这些代码我们就可以放在finally{}中。

Java的异常处理采用的是终止模型,即如果try块中的某处出现了异常,则立刻停止当前程序的运行,在堆中创建对应的异常对象,异常的处理转入到异常处理代码处(即对应的catch块),执行完异常处理代码后,try块中出现异常处之后的程序将不会被执行,程序会跳出try块,执行try块之外的程序。

例子:覆盖知识点:①执行对应的catch;②一定执行finally中代码;③try出异常之后的代码不再执行;

public String testException_one(){
        String str = "aaa";
        try {
            str += "bbb";
            int a = 1/0;
            str += "ccc";
        } catch (NullPointerException e) {
            str += "ddd";
        } catch (ArithmeticException e) {
            str += "eee";
        } finally {
            str += "fff";
        }
        str += "ggg";
        return str;
    }

程序执行返回结果:aaabbbeeefffggg

注意:没有输出ccc和ddd。

结果分析:上面的程序进入try块后,连接了bbb,然后遇到1/0抛出ArithmeticException 异常,首先NullPointerException所在的catch块不匹配该异常,然后检查到ArithmeticException 所在的catch块匹配该异常,进入该catch块内进行异常处理。执行完所在的catch块,一定会执行finally块,但是try块报异常行之后的代码不会再执行,直接跳出try块,继续执行try…catch…finally之后的代码。

3. 继承和实现接口时的异常限制

class OneException extends Exception{}
class TwoException extends Exception{}
class OneSonException extends OneException{}
class TwoSonException extends TwoException{}

interface Worker {
    void work() throws TwoException;

    void say() throws OneException;
}

class Person {

    public Person() throws TwoException {
        System.out.println("Person Constructor...");
    }

    public void eat() throws OneException {
        System.out.println("Person eat...");
    }

    public void say() throws TwoException {
        System.out.println("Person say...");
    }

}

public class Coder extends Person implements Worker {

    /**
     * 此处的TwoException是必须的,因为Person的构造函数中抛出了TwoException。
     * Coder在调用父类构造函数时,也必须抛出次异常,且不能是其子类异常.另外,构造函数可以抛出比父类多的异常。
     * @throws TwoException
     * @throws OneException
     */
    public Coder() throws TwoException, OneException {
        super();
    }

    /**
     * 实现的接口的方法或者重写的父类的方法可以抛出原方法的异常或其子类异常或者不抛出异常,
     * 但是不能抛出原方法没有声明的异常。这样是为了多态时,当子类向上转型为基类执行方法时,基类的方法依然有效。
     */
    public void work() throws TwoSonException {
        // TODO Auto-generated method stub
    }

    /**
     * 在接口和父类中都有该方法,且异常声明不是同一个异常,则该方法的声明不能抛出任何异常,
     * 因为子类中的该方法在多态时必须同时满足其实现的接口和继承的基类的异常要求。不能抛出比基类或接口方法声明中更多的异常。
     */
    public void say(){

    }

    /**
     * 基类中eat方法抛出了异常,在子类中覆盖该方法时,可以不声明抛出异常
     */
    public void eat(){

    }
}
/**同时还应该注意,如果方法声明抛出的是RunTimeException类型的异常,不受以上的限制;
只有检查型异常才受以上限制。非检查型异常由于系统自动捕获,不受任何限制。
*
*/

4. finally一定会执行

①break/continue/while:如下面例子中所示在循环中遇到continue或break时,finally也会执行。

public void testException_two(){

        for(int i = 0; i < 5; i++){
            try {
                if(i == 0){
                    continue;
                }
                if(i == 1){
                    throw new Exception();
                }
                if(i == 3){
                    break;
                }
                System.out.println("try..." + i);
            } catch (Exception e) {
                System.out.println("catch..." + i);
            } finally {
                System.out.println("finally..." + i);
            }
        }

    }
    /*
    执行结果:
finally...0
catch...1
finally...1
try...2
finally...2
finally...3
         */

②return:即使在try块中正常执行了return,finally也在return之前执行了。如下面例子所示:

    public void testException_three(){
        int a = 1;
        try {
            System.out.println("try...");
            return;
        } catch (Exception e) {
            // TODO: handle exception
        } finally{
            System.out.println("finally...");
        }
    }
    /*
执行结果:
try...
finally...
     */

③还有一种情况是:当try块抛出异常时,如果没有catch块能捕获到该异常,则该异常会被抛至上一级,在被抛至上一级之前,finally块会被执行,然后异常才会被抛至上一级。这个请有兴趣的同学自己验证吧。

总之,finally中的代码是一定会被执行到的。

5. finally中丢失异常

因为finally的特殊性,还会造成异常丢失的情况,如果在finally中抛出异常或者在finally中使用了return,则在try块中抛出的异常将会被系统丢掉。如下面代码所示(OneException和TwoException的定义在上面异常限制一节中已经给出):

    public void testException_finally_one(){
        try {
            System.out.println("test finally...");
            try {
                if(1 == 1){
                    throw new OneException();
                }
            }finally{
                throw new TwoException();
            }
        } catch (Exception e) {
            System.out.println("e.getClass: " + e.getClass());
        }
    }
    /*
     *
     执行结果输出:
test finally...
e.getClass: class com.synnex.demo.TwoException
     */

    public void testException_finally_two(){
        try {
            System.out.println("test finally...");
            try {
                if(1 == 1){
                    throw new OneException();
                }
            }finally{
                return;
            }
        } catch (Exception e) {
            System.out.println("e.getClass: " + e.getClass());
        }
    }

    /*
     执行结果输出:
test finally...
     */

6. finally造成的返回值困惑

下面进入到本篇开始的那个例子的解惑。

例子:

    public int testException_finally(){
        int x;
        try {
            x = 1;
//int y = 1/0;  //放开此处,出现ArithmeticException。

/*//注释掉 int y = 1/0;处,放开此处,出现NullPointerException
            String str = null;
            str.substring(0);
            */
            return x;
        } catch (ArithmeticException e) {
            x =2;
            return x;
        } finally{
            x = 3;
        }
    }

答案:

如果正常执行,返回值为1;

如果抛出ArithmeticException,返回值为2;

如果抛出其他Exception,则抛出该Exception,无返回值。

解惑:这是我根据《深入理解Java虚拟机-JVM高级特性与最佳实践》第二版书中的例子(P187~P188)做了一些修改。出现这种情况的原因是:在没有出现异常的情况下,先执行了x=1;然后执行return x;时,首先是将x的一个副本保存在了方法栈帧的本地变量表中,执行return之前必须执行finally中的操作:x=3;将x的值设置为了3,但是return时是将本地变量表中保存的x的那个副本拿出来放到栈顶返回。故没出异常时,返回值为1;出ArithmeticException异常或其子类异常时,返回值是2;如果出现非ArithmeticException异常,则执行完x=3之后,将异常抛出至上一层,没有返回值。

对字节码命令熟悉的朋友可以使用javap -verbose等命令反编译出该方法的字节码命令和异常表,从字节码层面上就能清晰的看出执行过程了。我对字节码命令知道得还不够多,只能从大体上解释这种运行过程。以后字节码命令学得自认为可以了的时候,也会写字节码相关的文章出来。希望这篇文章能帮到一些人理解Java的异常处理机制。

参考文章及书籍:

Java异常的深入研究与分析

《Java编程思想》第四版中文版第十二章通过异常处理错误

《深入理解Java虚拟机-JVM高级特性与最佳实践》第二版 第六章类文件结构 周志明著

Java异常处理机制难点解惑-用代码说话的更多相关文章

  1. java异常处理机制 (转载)

    java异常处理机制 本文来自:曹胜欢博客专栏.转载请注明出处:http://blog.csdn.net/csh624366188 异常处理是程序设计中一个非常重要的方面,也是程序设计的一大难点,从C ...

  2. JAVA 异常处理机制

    主要讲述几点: 一.异常的简介 二.异常处理流程 三.运行时异常和非运行时异常 四.throws和throw关键字 一.异常简介 异常处理是在程序运行之中出现的情况,例如除数为零.异常类(Except ...

  3. Java 异常处理机制和集合框架

    一.实验目的 掌握面向对象程序设计技术 二.实验环境 1.微型计算机一台 2.WINDOWS操作系统,Java SDK,Eclipse开发环境 三.实验内容 1.Java异常处理机制涉及5个关键字:t ...

  4. Java异常处理机制 —— 深入理解与开发应用

    本文为原创博文,严禁转载,侵权必究! Java异常处理机制在日常开发中应用频繁,其最主要的不外乎几个关键字:try.catch.finally.throw.throws,以及各种各样的Exceptio ...

  5. 如何正确使用Java异常处理机制

    文章来源:leaforbook - 如何正确使用Java异常处理机制作者:士别三日 第一节 异常处理概述 第二节 Java异常处理类 2.1 Throwable 2.1.1 Throwable有五种构 ...

  6. 【转】深入理解java异常处理机制

    深入理解java异常处理机制 ; int c; for (int i = 2; i >= -2; i--) { c = b / i; System.out.println("i=&qu ...

  7. Java异常处理机制及两种异常的区别

    java异常处理机制主要依赖于try,catch,finally,throw,throws五个关键字.   try 关键字后紧跟一个花括号括起来的代码块,简称try块.同理:下面的也被称为相应的块. ...

  8. java异常处理机制详解

    java异常处理机制详解 程序很难做到完美,不免有各种各样的异常.比如程序本身有bug,比如程序打印时打印机没有纸了,比如内存不足.为了解决这些异常,我们需要知道异常发生的原因.对于一些常见的异常,我 ...

  9. Java异常处理机制的秘密

    一.结论 这些结论你可能从未听说过,但其正确性是毋庸置疑的,不妨先看看: 1.catch中throw不一定能抛回到上一层,因为finally中的return会抑制这个throw 2.finally中t ...

随机推荐

  1. ubuntu12.04更新到14.04,win7误删BCD引导项,导致两个系统都无法进入

    解决办法: 制作老毛桃U盘启动盘,使用BCD编辑软件,对C/boot下的BCD文件进行编辑,添加win7引导向. 开机进入win7后,使用easyBCD添加ubuntu14.04启动项,选择grub2 ...

  2. [LeetCode] Contiguous Array 邻近数组

    Given a binary array, find the maximum length of a contiguous subarray with equal number of 0 and 1. ...

  3. 爱奇艺2018春招Java工程师编程题题解

    字典序最大子序列 题目描述 对于字符串a和b,如果移除字符串a中的一些字母(可以全部移除,也可以一个都不移除)就能够得到字符串b我们就称b是a的子序列. 例如."heo"是&quo ...

  4. Linear Regression with Scikit Learn

    Before you read  This is a demo or practice about how to use Simple-Linear-Regression in scikit-lear ...

  5. ●POJ 2983 Is the Information Reliable?

    题链: http://poj.org/problem?id=2983 题解: 差分约束. 1).对于条件(P u v w),不难发现反映到图上就是: $dis[u]-dis[v]=w$,所以添加两条边 ...

  6. ●UVA 11796 Dog Distance

    题链: https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem& ...

  7. [Codeforces Round #431]简要题解

    来自FallDream的博客,未经允许, 请勿转载,谢谢. 好久没写cf题解了zzz 代码比较丑不贴了,cf上都可以看 Div2A. 给你一个长度为n(n<=100)的序列 判断是否可以分成奇数 ...

  8. bzoj1073[SCOI2007]kshort

    1073: [SCOI2007]kshort Time Limit: 20 Sec  Memory Limit: 162 MBSubmit: 1483  Solved: 373[Submit][Sta ...

  9. splay模板(BZOJ3224)

    用splay实现二叉搜索树的模板,支持插入,删除,找前缀后缀,x的排名以及第x名的数. #include <cstdio> #define l(x) t[x].s[0] #define r ...

  10. Spring使用@Scheduled定时调度

    一.spring配置文件中增加对这个注解的支持: 配置文件如下: <?xml version="1.0" encoding="UTF-8"?> &l ...