龙空技术网

“全栈2019”Java异常第二十章:如何自定义异常?

人人都是程序员 66

前言:

当前你们对“java中如何抛出自定义异常变量”可能比较着重,兄弟们都想要分析一些“java中如何抛出自定义异常变量”的相关知识。那么小编同时在网摘上网罗了一些对于“java中如何抛出自定义异常变量””的相关文章,希望小伙伴们能喜欢,姐妹们快快来学习一下吧!

难度

初级

学习时间

30分钟

适合人群

零基础

开发语言

Java

开发环境JDK v11IntelliJ IDEA v2018.3友情提示本教学属于系列教学,内容具有连贯性,本章使用到的内容之前教学中都有详细讲解。本章内容针对零基础或基础较差的同学比较友好,可能对于有基础的同学来说很简单,希望大家可以根据自己的实际情况选择继续看完或等待看下一篇文章。谢谢大家的谅解!1.为什么需要自定义异常?

这个问题的答案只有一个:现有的异常类不满足我们的需求。

其实怎么说呢,现有的异常类也是由JDK(Java 语言的软件开发工具包)开发人员定义的。

比如NullPointerException,为什么会有NullPointerException呢?

就是因为程序中总会碰到对象为null的情况:

这个时候就需要一个类来描述这个异常情况:

所以NullPointerException就应运而生了。

那我们自己什么情况下需要定义一个异常类呢?

这个需求就比较多了,这里只列举一个。

比如,我们来一个比较实际的,用户输入正方形的边长a,然后我们根据用户输入的边长a来计算此正方形的面积,大家都知道是a乘以a。

注意:这里的变量名a不是我随便取的,是数学中通常用字母a来表示边长。希望大家在给变量命名的时候遵守命名规范。

运行程序,执行结果:

程序写出了,看起来也没有什么问题,哪里会用到自定义异常呢?

如果我不小心传了一个负的正方形边长,大家觉得合理吗?肯定不合理。

运行程序,执行结果:

从运行结果来看,结果不是负数,那是因为两个负数相乘结果为正数。我们单单就这个边长为负数这个问题来看,程序就是有问题的,问题出在正方形的边长为负数这里,正方形的边长不能为负数。所以这里是一个异常情况而且还得告知用户,让他重新输入,并且输入的边长不能为负数。

综上所述,我们需要在squareArea()方法里面判断边长是否为负数,如果边长为负数,则抛出正方形异常SquareException,异常详细信息是边长为负数。

但是我们的正方形异常SquareException还没有定义呢,所以接下来我们的任务就是来学习如何定义一个异常。

2.异常体系

在学习自定义异常之前,我们先来看看异常体系是怎么样的,然后在了解异常体系之后就开始加入异常体系。

在Java异常这个大家族中,Throwable是这个异常家族中的老大,它下面有两个靠谱老弟,一个叫“Error”,一个叫“Exception”。Error老弟负责错误部门,Exception老弟负责异常部门,在Exception部门下面有一个特殊的弟弟,叫“RuntimeException”,RuntimeException负责运行时异常,它们各司其职。

异常体系结构图:

Throwable在《“全栈2019”Java异常第十六章:Throwable详解》一章中已经介绍过了;

Error在《“全栈2019”Java异常第十七章:Error详解》一章中介绍过;

Exception在《“全栈2019”Java异常第十八章:Exception详解》一章中介绍过;

RuntimeException在《“全栈2019”Java异常第十九章:RuntimeException详解》一章中介绍过。

3.加入异常体系

如果我们想自定义异常,那么就得加入异常这个体系中去。

加入异常体系的具体操作是什么?

加入异常体系的具体操作是继承异常类。

例如,我们的正方形异常SquareException:

注意:这里我们的正方形异常SquareException继承的是Exception,其实这并不是我们最后的写法,具体继承哪个异常类,请往后阅读。

有多少异常类可以被继承?

全部异常类都可以被继承。

全部异常类的结构图:

这些异常被分成了三类:

图中红色部分为“已检查异常”

第一种异常是已检查的异常。

定义:

出于编译时检查异常的目的,Throwable和Throwable的任何子类(除Error和RuntimeException的子类)都被视为已检查的异常。

图中绿色部分为“错误”

第二种异常是错误。

定义:

Error是错误。它是Java异常体系中的一种。

图中蓝色部分为“运行时异常”

第三种异常是运行时异常。

定义:

RuntimeException及其子类都是运行时异常。

图中紫色部分为“未检查异常”

其中错误和运行时异常是未检查异常。

定义:

Error及其子类和RuntimeException及其子类都是未检查的异常。

概念性的东西不需要大家死记硬背,接下来,我会逐个逐个讲解它们的应用场景,希望大家别急。

按照已检查异常和未检查异常分,我们的结构图是这样的:

图中红色部分是已检查异常,紫色部分是未检查异常。

按照已检查异常、错误和运行时异常分,我们的结构图是这样的:

图中红色部分是已检查异常,绿色部分是错误,蓝色部分是运行时异常。

请大家重点注意最后这张图,因为最后这张图才是重点!!!

4.该加入哪个异常阵营?

从上一小节的最后内容得知,我们异常体系中有三大阵营:以Throwable为首的已检查异常、以Error为首的错误和以RuntimeException为首的运行时异常。

图中红色部分为“已检查异常”,绿色部分为“错误”,蓝色部分为“运行时异常”

也就是上面这张图。

这三大阵营的老大,我们可以捋一下:

已检查异常:Exception。

错误:Error。

运行时异常:RuntimeException。

注:Throwable是整个异常体系的老大,实际开发中也不会去继承它,所以大家继承异常类的时候不必考虑Throwable。

下面通过三个小节分别来讲解三大异常阵营。

第5小节讲解什么情况下继承已检查异常。

第6小节讲解什么情况下继承错误。

第7小节讲解什么情况下继承运行时异常。

5.加入“已检查异常”阵营

什么情况下,建议加入“已检查异常”阵营?

首先,我们通过前面几章的学习知道已检查的异常就是应用程序中可以预测和恢复的异常。基于这点我们就了解到,当我们要定义的异常是可以提前预测的,而且还能通过try-catch或throws处理后,程序还能从异常情况中恢复到正常运行中来,这种情况就可以加入已检查异常阵营。

已检查异常的成员有很多,这里拿其中一个成员举例说明一下,比如官方定义的异常类FileNotFoundException(找不到文件异常)。

上述程序中我们创建了FileReader对象,在构造FileReader对象的时候需要传入一个文件路径,这里我们传的是“/a.txt”:

有可能这个文件路径是不存在的,那么程序就有可能发生FileNotFoundException异常,但是大家想一想:这个异常我们可不可以预测到?

答案是可以预测的,为什么是可以预测的呢?

因为我们可以假设用户输入的文件路径不存在,那么我们就可以通过异常来描述这个异常情况并且使用try-catch来处理这个异常,目的就是不让程序因为这个异常而停止运行。

综上所述,只要你的异常可以被提前预测和处理后能让程序正常运行的话,就可以加入已检查异常阵营,即继承Exception类及其子类。

所以,这里我们接着来考虑文章一开头的那个正方形异常的问题,你们认为正方形异常符不符合加入已检查异常的阵营情况?

这里是符合的,为什么呢?

因为这里的边长是用户输入的,我们可以预测到用户会输入一个不合法的数(比如负数),当程序遇到此类异常时,我们也有能力处理它,让程序恢复到正常运行中来。所以,我们的正方形异常是属于已检查异常。

接下来,我们就来实现这个自定义异常。

演示:

请实现正方形异常SquareException。

请观察程序代码及结果。

代码:

SquareException类:

Main类:

结果:

错误信息:

文字版:

lab.SquareException: 边长为负数。

at main.Main.squareArea(Main.java:37)

at main.Main.main(Main.java:18)

正方形的面积为: 0

从运行结果来看,我们自定义的异常正常被抛出,和现有的异常类特性一样。我们来分析一下程序代码。

首先,从自定义异常SquareException类看起:

SquareException类里面有两个构造方法,一个是无参构造方法,一个是带异常详细信息的构造方法:

无参构造方法是必须要的,同时大家也要注意方法内部要调用父类的无参构造方法;

带异常详细信息的构造方法是因为我们程序中有使用到:

这里我们在抛出SquareException异常对象时,使用到SquareException类的带参构造方法。

下面,我们来说说Main类:

来看squareArea()方法:

squareArea()方法内部,首先就判断边长a是不是小于0(负数):

当if语句为true时,也就是当边长为负数时,我们抛出SquareException异常:

当然了,对于方法内部抛出的异常我们可以try-catch和throws,这里我们选择throws,原因是我们方法内部无法去处理这个异常,应该将这个异常告诉调用者,让调用者去处理:

而我们的调用者也对此异常进行了处理,处理方式是try-catch:

这里我们可以看到catch代码块里面的具体处理方法是调用其异常的printStackTrace()方法,打印堆栈跟踪信息:

对应着程序执行结果的这一部分:

当然了,我们还可以这样处理:

将我们的面积变量area的值设置为0。

上面是area变量在try外面的情况,如果area变量在try里面,输出正方形面积的语句也在try里面,又可以怎么处理呢?

运行程序,执行结果:

程序案例就讲解到这里,另外,我们的自定义异常SquareException类还没有完善。

大家知道,前面我们学过异常链,也知道如何去构造异常链,这里我们就重写了带异常原因的构造方法,目的就是为了构造异常链:

为了异常详细信息和异常原因能一起传进来,于是我们将两个参数的构造方法也重写了:

最后,我们还需重写一个构造方法,这个构造方法它有四个参数:

message 异常详细信息cause 异常原因enableSuppression 是否抑制异常传播writableStackTrace 是否记录堆栈信息

这个构造方法一般很少用到,大家知道有这个构造方法就行,后面我们再详细讲解。

当然了,这都是演示例子中的自定义异常应用。实际项目开发中,我们的自定义异常的处理方式又会结合当时的情况有所不同,这里大家放心,后续的实战文章教学中会给大家结合实际案例讲解,这里大家暂时掌握这么多就足够了。

6.加入“错误”阵营

什么情况下,建议加入“错误”阵营?

首先,我们通过前面几章的学习知道错误是应用程序外部的特殊异常,应用程序通常无法预测或恢复。基于这点我们就了解到,当应用程序外部发生异常时,可以加入“错误”阵营,继承Error类及其子类。

什么叫“应用程序以外的异常”?

就是非我们自己写的程序里面发生的异常。比如我们写了一个读取文本文件内容的程序,程序正在运行,突然硬盘发生了故障,文本文件无法正常读取了,这就是一个应用程序以外的异常,用类来描述就是Error及其子类。

错误的成员也有很多,但是我们最常见的有一个,那就是内存溢出OutOfMemoryError。

小伙伴们,由于错误一般发生在应用程序之外,所以我们在日常开发中自定义异常很少去继承Error,这里就只假设一下程序中出现了错误。

演示:

请自定义一个继承自Error的异常。

请观察程序代码及结果。

代码:

DiskError类:

Main类:

结果:

从运行结果来看,我们只读取了文本文件里面一个字符就发生了硬盘错误,于是我们的程序就停止运行了。

简单分析一下程序,首先,我们定义了一个继承自Error的异常DiskError:

DiskError类里面该重写的构造方法和上一小节中的SquareException类一样。

接着,我们就在Main类的main()方法里面读取文本文件内容:

在读取文本文件内容的时候发生了硬盘错误:

执行的结果:

说实话,在实际开发中,很少有继承Error的异常,所以这里演示的例子毕竟是例子,不是实际案例,还请大家谅解。

7.加入“运行时异常”阵营

什么情况下,建议加入“运行时异常”阵营?

首先,我们通过前面几章的学习知道运行时异常就是在应用程序运行期间抛出的异常,这些是应用程序内部的特殊异常,应用程序通常无法预测或恢复。基于这点我们就了解到,当应用程序内部发生异常时,可以加入“运行时异常”阵营,继承RuntimeException类及其子类。

什么叫“应用程序内部异常”?

就是我们自己写的程序里面发生的异常。比如我们调用了一个为null的对象的方法,于是就发生了空指针异常NullPointerException。

运行时异常的成员有很多,我们日常开发中常见的有ClassCastException(类型转换异常)和NullPointerException(空指针异常)。

接下来,我们就来自定义一个运行时异常。

自定义运行时异常需要继承自RuntimeException及其子类:

这是一个除法运算异常DivException,类里面的内容和之前的自定义异常类一样,都是重写那五个构造方法。

接着,我们来演示一下用法。

演示:

请自定义一个运行时异常。

请观察程序代码及结果。

代码:

DivException类:

Main类:

结果:

从运行结果来看,我们自己定义的运行时异常起作用了。

简单的回顾一下程序代码,我们的自定义运行时异常类DivException就不细看了,直接来看Main类中的div()方法:

在div()方法里面我们判断了当除数y为0时的情况:

如果除数y为0,我们就抛出DivException异常:

因为我们抛出的是一个运行时异常,所以Java程序没有强制我们进行try-catch或throws处理。

接着,我们在main()方法里面调用div()方法:

然后打印了其结果:

对应着程序执行结果:

当然了,这个执行结果发生的地方不是在输出语句那里,是在我们div()方法抛出DivException异常对象那里,因为除数为0了。

程序分析到此告一段落。

最后

最后我想对大家说的就是,本篇文章可能有很多写得不好的地方,请大家谅解。

其次我想表达的是,很多写自定义异常的文章都仅仅写了继承Exception类,其实我们在写自定义异常的时候考虑的继承类还有很多,不仅仅是Exception类,大家要分情况而定,不是所有自定义异常都是继承自Exception类。

希望大家可以借鉴下面规律来写自定义异常:

应用程序内部发生可预测可恢复的异常请继承Exception类及其子类。应用程序外部发生不可预测不可恢复的异常请继承Error类及其子类。应用程序内部发生不可预测不可恢复的异常请继承RuntimeException类及其子类。总结现有的异常类不满足我们的需求时,就需要自定义异常。如果我们想自定义异常,那么就得加入异常这个体系中去。加入异常体系的具体操作是继承异常类。当我们要定义的异常是可以提前预测的,而且还能通过try-catch或throws处理后,程序还能从异常情况中恢复到正常运行中来,这种情况就可以加入已检查异常阵营。加入已检查异常阵营,即继承Exception类及其子类。当应用程序外部发生异常时,可以加入“错误”阵营,继承Error类及其子类。当应用程序内部发生异常时,可以加入“运行时异常”阵营,继承RuntimeException类及其子类。应用程序内部发生可预测可恢复的异常请继承Exception类及其子类。应用程序外部发生不可预测不可恢复的异常请继承Error类及其子类。应用程序内部发生不可预测不可恢复的异常请继承RuntimeException类及其子类。

至此,Java中自定义异常相关内容讲解先告一段落,更多内容请持续关注。

答疑

如果大家有问题或想了解更多前沿技术,请在下方留言或评论,我会为大家解答。

上一章

“全栈2019”Java异常第十九章:RuntimeException详解

下一章

“全栈2019”Java异常第二十一章:finally不被执行的情况

学习小组

加入同步学习小组,共同交流与进步。

方式一:关注头条号Gorhaf,私信“Java学习小组”。方式二:关注公众号Gorhaf,回复“Java学习小组”。全栈工程师学习计划

关注我们,加入“全栈工程师学习计划”。

版权声明

原创不易,未经允许不得转载!

标签: #java中如何抛出自定义异常变量 #java中如何抛出自定义异常变量的方法 #java中如何抛出自定义异常变量的方法有哪些