1、什么是异常
在生活当中,不管是人还是动物又或是植物,都会生病;在程序中也是,作为程序猿,虽然我们会尽力将程序写的完美,可难免会出现一些问题~
在程序执行过程中,发生的一些不正常行为,就叫做异常。
2、异常的体系结构
我们可以观察到,Throwable是异常体系的顶层类,其派生出两个子类Exception(异常)和Error(错误)。
Exception:就是我们平时说的异常,可以理解为我们现实生活中的生病,我们可以通过代码来处理异常,使程序正常运行。
Error:指的是Java虚拟机无法解决的严重问题,比如:JVM的内部错误、资源耗尽等,例如:StackOverflowError(栈溢出错误),一旦发生就回力乏术。
3、异常的分类
Exception继承于Throwable,也就是说,异常其实是一个类,而异常又分为运行时异常(非受查异常)和编译时异常(受查异常)。
3.1 运行时异常(非受查异常)
其实大家对非受查异常并不陌生,我们平时遇到的例如:数组越界异常、空指针异常、算数异常、类型转换异常等都是非受查异常。
算数异常:
数组越界异常:
空指针异常:
3.2 编译时异常(受查异常)
在程序编译期间发生的异常,称为编译时异常,也称为受检查异常。
例如当我们拷贝自定义类型的对象时,我们没有在main方法中声明异常,就会划红线报错:
受查异常必须进行处理,否则程序无法运行。
抛出异常后,异常警告消失,程序可以执行了。
但是需要注意的是,我们这里只是声明了异常,并没有处理掉这个异常,如果出现了异常,只会交给JVM来处理。一旦交给JVM处理,程序就会立即终止。
那,我们该如何去处理异常呢?
4、异常的处理
在Java中,异常处理主要用到5个关键字:throws、try、catch、throw、finally
4.1 throws 声明异常
我们在上述举例受查异常时就已经提到了throws关键字,当程序中可能会抛出编译时异常时,我们可以使用throws来声明这个异常,告诉调用者:"你要帮我处理掉这个异常!"
也就是说当前方法不出理这个异常,而提醒方法的调用者,让调用者帮它处理。
当前方法不处理异常而使用throws声明异常后,那该方法的调用者只有两条路走:
1. 老老实实的帮它处理掉这个异常(使用try{}catch{} ,下面会讲)
2. 不想帮它处理异常,也使用throws来声明这个异常(就是我们前面举例受查异常时所用到的解决方法)
也就是说,当编译时异常出现后,必须进行处理!(方法内部处理,或者方法调用者来处理,总之必须处理!)否则程序无法运行!
对于throws关键字有以下几点值得注意:
1. throws必须跟在方法的参数列表之后
2. 声明的异常必须是 Exception 或者 Exception 的子类
3. 方法内部如果抛出了多个异常,throws之后必须跟多个异常类型,之间用逗号隔开,如果抛出多个异常类型 具有父子关系,直接声明父类即可
4. 调用声明抛出异常的方法时,调用者必须对该异常进行处理,或者继续使用throws抛出
4.2 try-catch 捕获处理异常
异常抛出后throws并没有处理异常,只是进行了声明,要想捕获处理异常,需要用到try-catch。
语法格式:
我们在try{ }代码块中放入可能出现异常的代码,使用catch来进行捕捉,
代码举例:
我们来运行上图的代码:
需要注意的是:
1. 当程序抛出异常后,如果catch中有该异常的捕捉,则程序会直接跳到这个catch块执行catch中的代码,并从这个catch块继续往下执行(程序不会异常终止)。如果catch中没有捕捉到该异常,则会交给JVM来处理,程序也会立即终止。(也就是说我们可以用catch来捕捉多个异常,以免异常被交给JVM处理使程序异常终止)
2. try块中只会抛出一个异常,当异常被抛出,会立即来到对应的catch中进行捕捉,try块内抛出异常位置之后的代码将不会被执行,也就是说即使try块后面的代码有异常,也不会再抛出,所以不会抛出多个异常。
对于printStackTrace方法的作用,就是打印出该异常出现的位置,便于程序猿的发现,
如下图讲解:
try-catch知识点总结:
1. try块内抛出异常位置之后的代码将不会被执行
2. 如果抛出异常类型与catch时异常类型不匹配,即异常不会被成功捕获,也就不会被处理,继续往外抛,直到 JVM收到后中断程序----异常是按照类型来捕获的
3. try中可能会抛出多个不同的异常对象,则必须用多个catch来捕获----即多种异常,多次捕获
4.如果异常之间具有父子关系,一定是子类异常在前catch,父类异常在后catch,否则语法错误
5.由于 Exception 类是所有异常类的父类,因此可以用这个类型表示捕捉所有异常(可以放到最后用来兜底,但是极不推荐只使用Exception来捕获异常 )
4.3 throw 手动抛出异常
我们之前讲到的异常的抛出,都是由于触发了JVM的机制由JVM来抛出的异常,这里的throw关键字是用来手动抛出异常的
例如:
因为异常都是一个类,所以我们throw出相应异常的对象就可以,也可以在构造方法传入相关信息。
其实,throw主要用于抛出自定义类型的异常。
需要注意:
1. throw必须写在方法体内部
2. 抛出的对象必须是Exception 或者 Exception 的子类对象(不能抛出自定义类的对象)
3. 如果抛出的是 RunTimeException 或者 RunTimeException 的子类(运行时异常),则可以不用处理,直接交给JVM来处理(但程序会立即终止)
4. 如果抛出的是编译时异常,用户必须处理,否则无法通过编译
5. 异常一旦抛出,其后的代码就不会执行
4.4 finally
不管有没有抛出异常,是否被捕获,finally中的代码一定会执行的,一般在finally中进行一些资源清理的扫尾工作。
比如,当程序抛出异常时,要么异常被catch捕获,执行catch后代码;要么异常没有被catch捕获,程序异常终止。这两种情况都是会使程序的某些部分没有被执行,而程序中会有必要的部分必须被执行,例如:资源的关闭,那就可以把这段必须被执行的代码放入finally中。
其实,我们平时用的输入方法就是一种资源,我们利用finally来关闭它:
运行1:
运行2:
运行3:
我们发现,不管有没有抛出异常,也不管抛出异常后catch有没有捕获,哪怕是交给JVM来处理异常(程序异常终止),finally中的代码都被执行了。
我们还可以提前将方法返回:
我们发现,即使方法已经遇见return返回,后面finally中的代码仍然被执行了。
5、自定义异常类
Java当中虽说有着丰富的异常类,但是我们在开发过程中难免会遇见一些不能表示的异常,这时,我们就可以自定义异常。
如何自定义异常呢?
我们可以参考Java给出的异常源码(仅当参考):
仿照源码,创建自定义类,使之继承于Exception 或者 RuntimeException类,给出无参和带参的构造方法。
我们可以模拟实现用户登录界面,当用户名或者密码输入错误时,可以抛出自定义的用户名异常或者密码异常:
首先,写出自定义的用户名异常和密码异常:
当输入用户名或者密码错误时会抛出对应异常:
运行展示:
1.密码输入错误
2.用户名输入错误
3.输入正确
创建自定义异常类需要注意以下几点:
1. 自定义异常通常会继承自 Exception 或者 RuntimeException
2. 继承自 Exception 的异常默认是受查异常(必须捕获处理掉异常)
3. 继承自 RuntimeException 的异常默认是非受查异常
OK~本次博客到这里就结束了,
感谢大家的阅读~欢迎大家在评论区交流问题~
如果博客出现错误可以提在评论区~
创作不易,请大家多多支持~