【软件构造】关于Java的异常处理

        作为一个初学Java的小白,在软件构造课程开始前,只是学习了Java的基本语法,对于异常了解的也很少,这也让我在写实验时很是苦恼(悲),零零散散看了一些博客,也无法让我有很深刻的理解,所以我决定较为系统地学习一下Java的异常。(老杜yyds)
        我对于异常的第一印象就是代码出错时,那一坨“无情”的红色字符,异常其实就是程序执行过程中出现的不正常的情况,在出现这些不正常情况时,JVM会将异常信息打印到控制台,供程序员参考,所以异常是帮助程序员提高程序健壮性的好帮手。
        那么在Java中异常是以什么形式存在的呢?异常是以类的形式存在的,它就和别的类一样,也可以new对象,当程序中遇到异常情况时,JVM就会new一个对应异常的对象。


        既然异常也是以类的形式存在的,那我们就可以查一查它的家谱。如上图(丑陋),在Object类下有一个叫Throwable的子类,字面意思,它是所有可抛出的类的“祖宗”。Throwable有两个子类,Error类和Exception类,错误和异常都是可抛出的。先看一下Error,当出现错误时,程序会终止执行,JVM会退出,错误是无法处理的,比如我们熟悉的栈溢出错误,我们是无法处理的。接下来看异常,在查看JDK手册时会发现,Exception有巨量的子类,但其实这些子类可以分成两类:运行时异常和编译时异常。所有的RuntimeException及其子类都属于运行时异常,可以处理也可以不处理,编译器不会报错。RuntimeException类之外的其他异常都属于编译时异常,要知道的是,编译时异常不是出现在编译阶段(所有的异常都发生在运行阶段),而是所有的编译时异常都需要在编译时就进行处理,如果不处理,编译器会报错。
        那为什么要这样区分呢?编译时异常和运行时异常有什么区别呢?编译时异常一般发生的概率比较高。运行时异常一般发生的概率比较低。按照人的常规思维来看,我们一般只需要对发生概率高的事情进行提前的预防,比如天气可能会下雨,那我就带一把伞进行预防。对于概率低的事件,我们就不会花那么多心思去考虑,比如说我不会因为存在我一出门就会被雷劈的可能性,就把自己套在一个法拉第笼里。如果所有的可能出现的异常我们都要去预防,这一定是不现实的,也不是我们希望的。所以Java把发生概率高的异常拿出来,我们在编译时只需要对这些发生概率高的异常进行预处理即可,这就是编译时异常(又叫受检异常:Checked Exception)。发生概率低的叫运行时异常(又叫未受检异常:Unchecked Exception),我们可以不对其进行处理。
        我们已经知道了异常的区分,那么异常应该如何进行处理呢?有两种方式:第一种是在声明方法时throws异常,抛给调用者,抛出异常其实就是“甩锅”,出现了异常,我不处理,让调用者处理。第二种是利用try...catch捕捉异常,将异常彻底解决。借用课程中老杜的例子,如果我是一名公司职员,因为我的失职让公司损失了1000元,那么我有两种处理方式;一种是上报给领导,让领导处理(异常上抛);另一种是不让领导知道,我自己悄咪咪垫上1000元(异常捕捉)。异常上抛并没有彻底解决异常,只是把异常抛给了调用者,调用者仍需要从这两种方式中选一种处理异常,只有对异常进行捕捉,异常才是真正被解决了。如果异常一直上抛给main方法,且main方法也没有捕捉,继续上抛给JVM,JVM接受到这个异常,就会终止程序的执行。

·对于throws,我们还需要注意以下几点:

  1. 如果出现异常a,通过throws把异常a抛给了调用者,如果调用者继续上抛,上抛的异常可以是异常a,也可以是异常a的父类。

2、throws后可以跟多个异常类,中间用逗号隔开,throws 异常a,异常b。

·对于try...catch,我们还需要注意以下几点:

  1. 如果try中的程序出现了异常,被catch捕捉到了,那么就会执行catch中的代码,try中出现异常的语句之后的语句不会执行。捕捉异常之后,try...catch之后的语句都可以正常执行。

                try{

                语句1;

                语句2;

                }catch(异常类型 引用名){

                语句3;

                }

                语句4;

        例如上面的结构,如果语句一出现了异常,那么语句二就不会执行,而去catch中匹配,如果捕捉到异常,就执行语句三,然后执行语句四。

        2、可以有多个catch语句,如果出现异常,从上到下依次匹配,且注意需要“从小到大”排序,不能出现父类异常在上,子类异常在下的情况(否则子类永远不会被匹配到,编译器也会报错)。

        3、catch(异常类型 引用名),引用名指向接受到的异常对象,上面提到过如果遇到异常,JVM会new一个对应类型的异常对象出来,catch中接收的就是这个异常对象,所以引用名指向的就是该异常对象。

        4、如果一个方法选择上抛而不是接受异常,那么当一条语句出现异常时,该方法就会结束,不会执行后面的语句!

        5、catch小括号中的异常类型可以是可能发生的异常的父类,只要能捕捉到就可以。

        6、在Java8之后,catch小括号中可以用“|”来表示或,将几种异常类型统一处理。

                例如:

                                try{

                                语句1;

                                }catch(异常类型1|异常类型2|异常类型3 引用名){

                                语句2;

                                }
下面说一下finally字句,try可以和finally字句配合使用,可以有try...catch...finally或者try...finally,finally中的语句是一定会执行的。下面举几个例子帮助理解:

执行如上代码,输出的结果是1,3,4.

执行如上代码,输出的结果是1,4.

        那我们来猜一猜上面这个输出的结果是什么呢?是101,101吗?不是!!!执行后会发现,结果是101,100!101是finally中输出的,100是main方法中输出的,按理说a++发生在return前,但是为什么a是100呢?在这里,我们需要知道Java中的几个规则:1、Java语句的执行是从上到下执行的,return语句的上一句是int a=100,所以return的一定是100。2、return语句一执行,方法就会结束,而finally方法一定会执行,所以finally一定发生在return之前(从上面的那个例子也可以看到)。这里看起来似乎是矛盾的,如何才能同时满足这两条规则呢?我们可以看一下该程序生成的class文件的反编译结果:

        我们惊奇的发现,在反编译得到的程序中,声明了一个中间变量var1,将a的值赋值给var1,最后返回var1,而在finally中对a进行加一操作!SUN公司的程序员用这种方式同时满足了以上的两个规则,令人惊奇!

        如此看来,finally一定会执行,是吗?也不是。如果使用System.exit()退出JVM,finally语句就不会执行了。

        上面我们一直在对异常进行处理,那么这些异常来自于哪里呢异常是如何抛出的呢?我们跟踪某一个异常,就会发现在该异常内,用throw关键字抛出了异常。这就是异常的产生,SUN公司在其写好的异常内部抛出了异常,那我们也可以自己定义异常类型,自己抛出异常,都是一样的!我们可以用“throw new 异常类型()”的方式抛出异常,如果我们在一个方法中抛出了异常,那我们肯定不会在该方法内就捕捉这个异常(自己抛,自己捉,玩呢!),一定会把这个异常上抛给调用者,让调用者去处理。

我们也知道如何抛出异常了,那接下来我们就自己定义一个异常类吧。一共有两步:

第一步,编写一个类,继承Exception或者RuntimeException。(发生概率高就继承Exception,发生概率低就继承RuntimeException)

第二步,提供两个构造方法,一个无参数的,一个带有String参数的。

例如下图:

我们就可以用自己定义的异常了。

以上就是我根据老杜的课总结的关于异常的内容,写完这篇博客,也让我对异常有了更深的理解,感谢老杜!!!如果有写错的地方,也请大家指正(求饶)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值