目录:
一、异常的概念和体系结构:
1、异常的概念:
我相信,在我们在运行代码的时候,总会出现一些问题。所出现的这些问题,我们在Java中,我们将程序在执行中,出现的不正常的行为,称之为异常。
那么有几种异常在我们写代码的时候经常遇到,比如:
1、算数异常:
我们来看看,什么叫做算数异常呢,直接看代码:
2、数组越界异常:
我们在使用到数组的时候呢,是不是经常的遇到或听到数组越界这个词,那么它的异常是什么样的呢? 我们来看看啊,直接看代码:
当然在这个异常之后,再执行代码就不会再执行了。
3、空指针异常:
对于空指针还是很好理解的,我们看看代码,就可以看懂了: 对于这个之后要是有代码,也是不会执行的,所以在这里呢,我呢就不在进行举例了,和上面是一样的。
所以从这几个例子就可以看出,对于异常来说呢,对于不同的异常,就会出现不同的结果。
在Java中不同的异常,都有其对应的类来进行描述。
2、异常的体系:
对于异常来说,它有很多的种类,所以在Java中维护了一个异常的体系结构。我们看图片来看看是什么样的
那么这些都是什么意思呢,我知道你很急,但你先别急,现在我们来一一介绍:
Throwable:是异常类型的最顶层,它呢会派生出两个子类,这两个子类都是非常的重要的。
Error:这个就是Java虚拟机无法解决的问题,比如呢:内存的资源耗尽,JVM的内部错误,这些呢都是Error,最典型的,也是经常发生的就是:陷入死循环,使资源耗尽而出现的异常。
Exception:这个就是当程序产生异常后,程序员可以根据代码进行解决的异常,就是这个。
我们接下来看看。Exception这个异常的分支,都派生出的那两个子类都是什么意思。
3、异常的分类:
1、运行时异常/非受查异常:
这个异常呢,就是我们上面的那些算数异常、数组越界异常、空指针异常等。就是运行时异常,在不运行时不会报错,只有运行时才会报错。
2、编译时异常/受查异常:
这个呢,就是在我们讲抽象类和接口的时候呢,讲的深度拷贝就是一个例子,我们来重新看一下:
当然这种的就不是异常了,这个只是代码写的问题,不是异常。
那么我们在认识异常之后呢,我们就要想一想我们要是出现了Exception这种的异常的时候,我们要如何处理异常呢?接下来我们来看看:
二、异常的处理:
1、防御式编程:
错误的代码是客观的,我们要在出错前提示程序员,让其知道是哪里出现了问题,对于实现这种方法的方式主要有:
1)、 事前防御型(LBYL) :
这种的呢就是事前防御型的,处理异常的方法。
这个方式是有缺陷的:正常的代码流程和处理错误的流程混在一起,使代码过于混乱了。
我们接下来讲的就是我们Java中常用的方式。
2)、事后认错型(EAFP):
我们在事情发生后认错,比事前确认可不可以做,更容易获取原谅,也就是先斩后奏。
在这种方法中呢,我们就要了解并且熟悉Java中的一些处理异常的关键词:try、throw、catch、finally、throws 这5个关键字。我们来看看如何使用的:
还是同样对于微信的登陆:
这里我们看到了5个关键词的其中其中两个,那么这两个是什么意思,并且对于那3个又是什么意思呢?
在了解这个之前我们先来了解 事后认错型的优点什么,为什么我们在Java中要使用这个来处理程序的异常。
优点:正常的代码流程和处理错误的流程是分开的,这样呢会使程序员更加的关注正确的流程 代码,使得代码更加的清晰,可以更好的理解代码的流程和步骤。
接下来我们来看看它们5个的作用是什么,又是如何实现的。
2、异常的抛出:
在我们的程序出现错误的时候,这个时候呢,就需要把错误信息告诉程序员,出现了什么错误。
在Java中呢,我们可以使用throw关键字,来抛出一个指定的异常对象,将其信息告诉我们程序员。我们来看看如何使用这个throw这个关键字:
那我们看到这时,就会有人有疑问了,我们抛出的异常和编译器给我们的异常爆出的不是一样的吗?我们又为什么要这么写?当然不一样了,我们看看,我们这个代码可以如何更改: 由此我们就可以知道,我们抛异常的代码要如何写了吧~我们来看公式:
throw new XXXException("产生异常的原因");
当然对于我们使用throw抛出异常的时候我们呢,有一些注意事项要进行注意一下:
• throw要写在方法体的内部。
• throw抛出的对象必须是Exception本身又或者是Exception的子类对象,但是我们在抛异常的 时候要特殊注意一下如果抛出的是RunTimeException本身又或者是RunTimeException子类 的时候,可以不做任何的处理,可以交给JVM来进行处理。
• 如果我们抛出的是编译时异常,那么我们必须要进行处理,要不然代码无法编译。
• 当我们throw异常后,其后代码不会再执行。我们来看看:
现在呢,我们知道了对于异常的抛出,那么当我们抛出异常之后呢,我们是不是要捕获并且解决这个异常呢?那么我们接着往下看:
3、异常的捕获:
在我们Java中呢,我们有两捕获异常的方法:声明异常throws 和 try-catch捕获处理异常 。
我们分别来一一了解一下:
1)、声明异常throws:
这个方法是处在方法列表的后面,这个实在当用户不想解决这个代码的时候,我们可以使用这个方法,来把异常抛给方法的调用者来处理这个方法。就是当我们方法不想处理这个异常的时候,我们抛给方法的调用者来处理。
这个方法我们之前见过,在我们介绍编译时异常的时候就使用过。我们还是看这个例子:
那么假如我们也不想在调用者中进行处理呢?我们又要如何做到呢?我们可以继续声明异常:
但还我们需要注意的是当我们交给JVM来处理的时候呢,就会终止程序了。
当我们使用这个声明异常的时候呢,我们可以声明多个异常,比如:
我们需要叫逗号来进行分割,但是呢我们需要注意的是,当我们声明多个异常的时候呢,如果出现父子关系呢,我们可以直接声明父类就可以了。我们来举个例子:
继承IOEcepection这个异常类
这里我们就可以非常容易理解这个父子类关系了 。
由此上面的这些实例我们就可以知道我们throws声明异常的语法了:
修饰词 返回值类型 方法名(参数列表) throws 异常类型1, 异常类型2, 异常类型3..... {
}
在我们使用throws声明异常的时候要注意的是:
• throws必须使用在参数列表的后面。
• throws声明的异常必须是Exception本身或者是Exception的子类。
我们接下来要介绍的try-catch方法是非常重要的,它使我们对于异常的捕获并且处理的方法。
※ 2)、try-catch捕获并处理异常:
我们虽然在异常捕获的时候就说过,关于捕获异常有两种方法,但是到是当我们在了解throws方法之后呢,就知道了其并没有真正的处理异常,只是将异常声明,并且要抛给调用者来处理异常。所以当我们真正捕获异常的时候其实只有一个:try-catch方法。
try-catch方法:
try {
//可能发生异常的代码
}catch (异常类型 e) {
//对捕获的代码进行处理
}catch (异常类型 e) {
//对捕获的代码进行处理
}.......
我们先来看看这个是怎么使用的:
执行代码的结果:
我们对于代码进行更改再看看结果如何:
异常的处理方式:
在Java中我们有很多种的异常,不可能是每种异常都需要使用try-catch方法进行处理异常。我们呢要根据实际情况来决定。比如呢:
• 对于比较严重的异常(比如 和内存相关的),我们应该直接让程序崩溃,防止产生更严重的后果。
• 对于不是太严重的问题,我们可以捕获异常并通知程序员解决异常。
• 对于有些可能会恢复的异常呢,我们可以尝试进行重试代码。
注意:
• try内当抛出异常之后,在异常后面的代码就不会执行。
•
那么我们要怎样才能打印哪里出现的异常呢?很简单,我们直接在catch中实现:
这样做我们就可以得到错误的信息了。
• 在try当中我们可能会捕获很多的异常,那么我们必须要用catch多次接收,多次捕获
这样我们就可以在捕获 数组越界异常 还可以 捕获空指针异常 了。
那么看到这里可能呢就会有一些疑问了,我们可以接收多个异常的话,那我们可不可以一次性捕获多个异常呢?比如这样:
我们来看看输出的结果: 所以我们不会捕获多个异常的,这个是需要注意的。
对于这个代码我们是可以进行简化的,当然简化的不是最好的,为什么呢?简化之后我们代码不应该更简洁了吗?为什么不是最好的?我们来看看:
那为什么是不好的呢?这是因为,当我们打印处理.....异常的时候,那么我们是处理了 数组越界异常呢 还是 空指针异常呢 ?这就是个问题了。所以不是很建议这样去处理异常。但是也是可以写滴。
对于这个简写,有的人是不是会这样写:
如果是这样写的话,就更有问题了,对于Exception来说有很多的子类异常,我们使用这个的时候,就不知道到底是什么异常导致的了。我们再来看段代码:
Exception是NullPointerException的父类,当我们捕获到Exception这个异常了,它的子类就捕获不到了,所以相当于后面的catch就是多余的了。不能所有的异常都用Exception来接收。
但是呢,我们的代码可以这样写:
这样就是先进行接收空指针异常,在之后再进行接收Exception的这个父类异常进行兜底。
所以我们在catch是要先写子类再写父类,在有父子类关系的时候。
3)、fianlly
我们把其余的四个关键词都进行了了解,现在我们只剩下finally了,那么接下来我们来看看对于finally是怎样进行使用的,又有什么作用。
在了解之前,我们先来了解一下别的东西,在我们写程序的时候呢,我们肯定有一些特殊的代码,不论我们的程序能否正常执行,我们都需要执行这些代码,比如呢在程序中打开的资源:数据库,网络连接,在程序中不论是否正常,必须要对资源进行回收。在我们出现异常的时候就会退出try-catch方法,有些代码就不会执行,这时候就需要使用finally来解决之个问题。
我们来看看代码:
那么这个时候呢,就会有人问了我们的finally和在try-catch方法外面写的代码也可以执行,那么我为啥还要用finally呢?
那么我们在来看这个例子就知道了,我们为什么使用finally代码。 我们来看:
OK,我们可以看到这里的在try-catch之后的代码,没有被执行,内存流没有被释放,导致资源泄露。 但是fianlly里的代码还是被执行了。所以我们一般把对于资源的释放代码放在fianlly里,进行扫尾工作。我们来更改一下代码看看:
我们还可以对于这个代码进行一定的优化:
在这里我们有一个非常特殊的一个问题,我们来看: fianlly呢是做的一个是善后工作,可以理解为,把10给覆盖了,但是我们不要这么写代码,这是不好的。
4、异常的处理流程:
如果方法本身没有处理异常就会,向上传递,最后就会交给JVM,导致程序终止。
我们来看事例:
先是程序本身有没有处理异常,如果没有,就交给调用者,再看有没有处理异常,再到main函数中看看有没有处理异常,如果还是没有处理异常,就交给JVM来处理,但是在这会终止程序
所以我们总结可知:
• 程序先执行try中的代码
• 如果try中的代码执行出现异常,就会到catch中寻找和其出现异常匹配的异常
• 如果找到了,就会执行catch里面的方法,如果没有找到的话,就会向上传递到调用者
• 无论是否找到匹配的,finally的里的代码都会执行
• 如果上层调用者也没有处理异常,就继续往上层调用,一直到main中,如果还是没有处理异常的话,就会让JVM来处理,这是会使程序终止。
三、自定义异常:
在Java中呢,我们有很多的异常的类,但是呢,还是不能表示所有遇到的异常,所以这时候我们就要自己维护这些异常。
比如我们现在有很多的软件是不是都有登录这个操作啊。但是呢,在Java中没有系统自带的登录异常,所以这时候呢,我们就要自己维护一个登录异常的操作。
下面我们来看看,我们怎样来自定义登录异常的:
我们可以看到,我们代码出现了错误,这是为什么呢?这是因为我们继承的是Exception,使其自定义的异常称为编译时异常/受查异常,这时候呢因为我们没有声明异常,我们不知道是哪种异常,所以我们要在抛异常的方法后面声明可能会出现的异常,才可以。我们来看: 在我们自定义异常的时候呢,我们有一些注意事项:
• 在我们自定义异常的时候通常继承Exceprion或者是RuntimeException
• 在我们继承Exception时候呢,我们自定义的异常称为 编译时异常/受查异常。
• 在我们继承RuntimeException时候呢,我们自定义的异常称为 运行时异常/非受查异常。
在我们集成RuntimeException的时候呢,如果我们在抛异常的方法后面没有声明异常,也是不会出错的。 但是呢,一般我们还是声明一下异常比较好。
总结:
OK,这次的分享就到这里就结束了,在这个之后呢,对与我们Java的基础就告一段落了,接下来我们就要进入数据结构的章节了,让我们敬请期待吧。拜拜~~