7000字的文章,实属不易,如果看着合适请动动小手点点关注,点点点赞,点点收藏吧,谢谢!
不喜勿喷,谢谢!
使用工具 IntelliJ IDEA Community Edition 2023.1.4
使用语言 Java8/JDK1.8
目录
2.2.2 2.使用try-catch-finally语句处理异常
1.初识异常
1.1 异常概念
在生活中,异常情况随身都有可能发生。下面将从生活中的异常过渡到程序,帮助大家建立对异常的初步认识。
在日常生活中,几乎随时随处可能出现意外情况。以学校生活为例,在上学的路上发生交通事故或拥堵、教员生病、教室投影不能正常使用、机房网络感染病毒等,这些都是意外情况。虽然这些意外情况是偶然事件,但是一旦发生就会带来不小的麻烦,影响正常的学习生活。同理,在程序中也会发生类似的情况,下面看看程序中的异常。
在程序开发过程中, 程序员虽然会尽量避免错误的发生,但是总会遇到一些不可预期的问题,如除法运算时除数为0、数组下标越界、数据类型不一致、内存不足、栈溢出等,这些就是异常。
1.2 计算机两大杀手
在计算机的发展中有两大计算"杀手",一个是断点,另外一个是除数为0.因为除数在为0在数学的解是无穷大,对于计算机来说,如果是无穷大,则意味着内存将全部占满,所以,在程序开发中,务必避免除数为0的情况。
1.3 使用if-else替代异常的缺点
- 无法穷尽所有的异常情况(因为受程序员知识的限制,异常情况总比可以考虑到的情况多,总会有"漏网之鱼",所以程序总不够健壮);
- 影响程序可读性,维护难度高(这种错误处理和业务逻辑混杂的代码严重影响程序的可读性,增加程序维护的难度。
2.异常处理机制
在程序设计时,必须考虑到可能发生的异常事件并进行相应的处理,这样才能保证程序正常运行。
Java的异常处理机制也秉承着面向对象的基本思想,在Java中,所有的异常都定义为类。除了内置的异常类,Java也可以自定义异常类。此外,Java的异常处理机制也允许自行抛出异常。
2.1 异常处理结构
2.1.1 异常处理结构语法
Java针对异常的处理提供了try、catch、finally、throws。throw五个核心关键字,其中前三个关键字就可以组成常用的异常处理结构,语法如下:
try{
//有可能出现异常的语句
}
[catch(异常类型 异常对象){
//异常处理语句
}]
[finally{
//一定会允许到的语句
}]
其中,try语句用于监听,将可能抛出异常的代码放在try语句块内,当try语句块内发生异常时,异常就会被抛出;catch语句用于捕获异常,catch语句用来捕获try语句块中抛出的异常;finally语句块总会被执行,主要用于回收try语句块内打开的资源,如数据库连接,网络连接和磁盘文件。
2.1.2 异常格式的常见组合
以上个事中的catch语句、finally语句都可选。实际上,这并不是表示catch语句、finally语句可以同时消失。异常格式的常见组合有try-catch、try-catch-finally、try-finally三种。
2.2 在程序中处理异常
2.2.1 使用try-catch语句处理异常
以上程序使用了异常处理语句,当程序出现异常时,异常会被try语句监听到,然后被JVM(Java虚拟机)抛出,被catch语句捕获进行处理,执行catch语句内的异常处理代码,不再需要程序员自行编写if语句进行判断,简化了代码。
使用Java异常处理机制的目的是帮助程序员发现异常并解决异常。以上代码中采用输出错误提示的发生处理异常,不能明确地描述异常类型,不能精确定位问题所在。更好的方法是使用异常类中提供的printStackTrace()方法进行异常信息但完整输出。
捕获异常中的异常对象.printStackTrace();即可进行异常信息的完整输出。
从上述代码可以看出,Java异常处理机制可以自动捕获不同类型的异常。如果try语句块在执行过程中遇到异常,则异常处之后的代码将不再被执行,系统会将异常信息封装成相应类型的异常对象,包含异常的类型、异常出现时程序的运行状态及对该异常的详细描述。如果这个异常对象与catch语句中声明的异常类型相匹配,则会被自动捕获,Java异常处理机制把该异常对象赋给catch关键字后的异常参数,执行catch语句块中的语句。Exception类型的常用方法如下:
void printStackTrace()
输出异常堆栈信息。堆栈信息中包含程序运行到当前类的执行流程,它将输出从方法调用处到异常抛出处的方法调用序列。
String getMessage()
返回异常的详细信息。该信息描述异常产生的原因,是printStackTrace()方法输出信息的一部分。
因为使用printStackTrace()方法输出的异常信息是最完整的,所以后续会使用该方法进行异常信息输出。在其输出结果中,可以自下向上观察程序的运行轨迹,最终定位到异常发生的位置。
2.2.2 2.使用try-catch-finally语句处理异常
如果程序出现异常,程序将自动跳转到catch语句块处理异常,后续输出语句将不再被执行。如果需要继续执行后面的语句,则需要添加finally语句。
finally语句块中的代码,无论是否出现异常,最终都会执行finally语句块的代码。
即使在try语句块或catch语句块中添加了return语句,finally语句块也会被正常执行。
那么,是否有finally语句块不被执行的情况呢?
答:在异常处理代码中执行System.exit(1),将退出JVM。
2.3 多重catch语句
在之前的案例中,程序出现的全部异常由统一的处理方式。但在实际开发中,有时会希望针对不同的异常类型采取不同的处理方式,这样就需要使用多重catch语句进行异常处理。语法如下:
try{
//有可能出现异常的语句
}
[catch(异常类型1 异常对象){
//异常处理语句1
}]
[catch(异常类型2 异常对象){
//异常处理语句2
}]
[finally{
//一定会允许到的语句
}]
算数异常(ArthmeticException)
输入类型不匹配异常(InputMismatchException)
- Exception是所有异常类型的父类,所以能够被该catch语句全部捕获,执行相同的异常处理程序。如果需要针对可能发生的异常类型采取不同的处理方式,则需要使用多个catch语句块分别捕获不同的异常类型,输出不同的提示信息。
- 最后一个catch语句使用Exception类型作为参数,因为它是所有异常类型的父类,可以处理前面的catch语句未匹配的异常。
- 这样,当在try块中出现异常时,所封装的异常对象就会从多重catch语句所提供的参数类型中找到能够匹配的一个,执行其中的异常处理代码。
- 当try语句块中发生异常时,系统将会按照从上到下的顺序依次检测每个catch语句,当匹配到某条catch语句后,后续其他catch语句块将不再被执行。
- 以Exception作为参数的catch语句必须放在最后的位置,否则所有的异常都会被其捕获,后面以其子类异常作为参数的catch语句将得不到被执行的机会。
2.4 异常分类及处理流程
前面已经介绍了异常处理及其对程序正常执行的重要性。为了能更好地理解异常的继承结构,必须理解异常的继承结构及处理流程。
所有的异常类型都继承Java.lang.Throwable类,它有两个重要的子类,分别是Error类和Exception类。
2.4.1 Error类和Exception类
2.4.1.1 Error类
Java.lang.Error类包含仅靠程序本身无法恢复的严重错误,是Java运行环境的内部错误或硬件问题,如内存资源不足、JVM错误等。应用程序不应该抛出这种类型的对象(一般由JVM抛出)。假如出现这种错误,除尽力使程序安全退出外,其他方法是无能为力的。因此在程序设计时,应该更关注Exception类。
2.4.1.2 Exception类
Java.lang.Exception类是程序本身可以处理的异常,可分为运行时(RunTimeException)异常与检查(Checked)异常。
2.4.1.2.1 运行时异常
可以在程序中避免的异常。这类异常在编译代码时不会被编译器检测出来,可以正常编译运行,但当程序进行时发生异常,会输出异常堆栈信息并中止程序执行,程序员可以根据需要使用try-catch语句捕获这类异常,如空指针异常、类型转换异常、数组越界异常等,这些异常包括Java.long.RuntimeException类及其子类,通过这些具体的异常类型,能够判断程序的问题所在。Java程序中常见的运行时异常如下:
常见异常
ArithmeticException
当出现算数错误时,抛出此异常。例如,在一个整数"除以0"时,抛出此异常
ArrayIndexOutOfBoundsException
当非法索引访问数组时,抛出此异常。例如,索引为负或大于等于数组长度
ClassCastException
当试图将对象强制转换为非本对象类型的子类时,抛出此异常
IllegalArgumentException
当向方法传递了一个不合法或不正确的参数时,抛出此异常
InputMismatchException
当欲得到的数据类型和实际输入的类型不匹配时,抛出此异常
NullPointerException
当应用程序试图在需要对象的地方使用null时,抛出此异常
NumberFormatException
当试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出此异常。例如,把"ABC"转换为数字
2.4.1.2.2 Checked异常
除运行时的异常,是由用户错误或问题引起的异常,这是程序员无法遇见的。在编译时,编译器会提示这类异常需要捕获。如果不进行捕获,则会出现编译错误。常见的编译异常由FileNotFoundException(Java字节输入流/Java字节输出流)异常、SQLException(Java连接数据库)异常等。
FileNotFoundException异常和IOException异常为Checked异常,必须进行异常处理才能通过编译。
在Java中进行异常类子类命名时都会使用XXXError或XXXException的形式,这样也是为了从名称上帮助开发人员进行区分。
2.4.2 Java异常处理流程总结
- 生成异常对象。如果程序在运行规程中出现异常,则JVM自动根据异常的类型实例化一个与之类型匹配的异常对象。如果发生异常的语句存在于try语句块中,则去匹配对应的catch语句;否则由JVM进行默认处理,输出异常现象后中断执行。
- 执行catch语句块,将所生成的异常对象匹配,则执行该catch语句块进行异常处理;否则,继续向下匹配其他catch语句块。如果无匹配的catch语句块·,则此异常对象交由JVM进行默认处理,输出异常信息。
- 执行finally语句块。无论最终是否有匹配的 catch语句块,都会执行finally语句块;如果没有finally语句块,则忽略。
整个过程类似于方法传参,所生成的异常对象根据catch语句后面的参数进行匹配,依据Java面向对象中向上转型的原理,所有异常对象都可以向其父类进行类型转换,所以所有的异常对象都可以使用Exception对象来接收。当无需对异常进行细分处理时,就可以简单地实现异常处理;否则,就需要使用多重catch语句对每类异常分别处理。
2.4.3 声明异常——throws关键字
如果根据程序的设计,不适合立即去处理异常,而应声明可能发生异常,交由上一级(声明的异常)处理,此时,就需要使用throws关键字。
2.4.3.1 throws语法
throws关键字是方法可能抛出异常的声明,用在所声明的方法时,表示该方法可能会抛出此类异常。语法如下:
public void 方法名() throws 异常类型[,异常类型]{
//方法体
}
throws关键字可以在一个方法定义处声明一类或多类异常,多类异常间使用逗号分隔。
2.4.3.2 调用声明异常方法的处理方式(不处理会报错)
声明异常后调用者有以下两种处理方式。
- 通过try-catch语句处理异常
- 通过throws语句继续声明异常,交由上一级处理,如果main()方法继续声明异常,则交由上一级调用者,在main()方法中可能不进行异常处理,而由JVM处理,如下所示
public static void main(String[] args) throws Exception{
//声明异常由JVM处理
}
throws 关键字用于在方法声明中指定该方法可能抛出的异常。当方法内部抛出指定类型的异常时,该异常会被传递给调用该方法的代码,并在该代码中处理异常。
从实际开发的角度来讲,main()方法不建议声明异常,因为如果程序出现了错误,会导致程序中断执行。
2.4.3.3 声明异常的好处
用这个的好处是如果有多个方法都可以用一个try语句来进行异常处理然后抛出异常(在后面的Java多线程中有用)。
- 在异常方法里面声明对应的可能发生异常就行。
- 然后在try语句可以使用多重catch来进行异常检测之后找到对应的然后抛出catch的语句块。
- 如果声明多个异常只要对应其中一个就会走进去,找到对应的catch然后抛出对应的catch语句块。
- 可以把Excetion放在最后一个catch语句中进行异常处理,可以避免意料之外的异常。
2.4.4 抛出异常——throw关键字
使用try-catch-finally语句进行异常处理,在这个异常处理结构中,由JVM进行判断并抛出异常信息,也就是程序遇到异常时会自动处理。但是有时可能会出现如下状况:
- 需要根据程序逻辑自定义异常类,这些异常类在Java异常体系中并未提供,不能抛出。例如,当程序中的注册流程执行时发生错误,需要抛出自定义的注册异常精准定位问题所在,但是注册异常在Java异常体系中不存在,就不能被自动捕获。
- 根据业务需要自行选择异常抛出时或自定义异常处理逻辑。例如,根据不同的逻辑判断条件而抛出异常。
在以上情况中,就需要使用throw关键字自行手动抛出异常,由调用者处理抛出异常的时机和异常的处理逻辑。由于使用throw关键字抛出的异常是异常类的对象,所以可以使用new关键字创建异常类的对象,通过throw关键字抛出,语法如下:
throw new 异常名([参数列表]);
使用throw关键字抛出的只能是Throwable类或其子类的对象,下面的语句是错误的
throw new String("exception");
因为String类不是Throwable类的子类
上面的代码并不难理解,因为当异常发生时一定会由系统产生一个异常类的实例化对象,只是此时异常类的实例化对象是通过手动创建并使用throw关键字抛出的而已。
2.4.5 自定义异常
使用自定义异常时最后需要在对应的catch语句块中点打印异常信息方法(printStackTrace);
或者用throws关键字吧可能发生的异常声明为异常的父类Exception,这样就可以使用Exception直接调用打印方法,打印的是对应异常的方法
2.4.5.1 自定义异常的三个步骤
当Java异常体系中提供的异常类型不能满足程序的需要时,可以自定义异常类,使用自定义异常一般有三个步骤:
- 定义异常类,继承Exception类或RuntimeException类;
- 编写异常类的构造方法,并继承父类的实现。常见的构造方法有四种形式,可根据需求选择添加实现。构造方法如下所示;
- 实例化自定义异常对象,并使用throw关键字抛出。
2.4.5.2 throw关键字的应用场景
使用throw关键字可以抛出自定义异常,那么自定义异常有哪些应用场景呢?
- 项目开发一般由团队成员共同完成,为统一对外异常展示的方式,可以使用自定义异常。
- 项目中因业务逻辑错误需要抛出异常,但一般是符合Java语法的,所以在Java中不会存在这类异常。例如,年龄异常、性别异常等。
- 使用自定义异常可以隐藏底层异常,精确定位,异常信息更直观。自定义异常可以抛出明确的异常信息,根据异常名也可以区分其发生位置,方便进行程序的修改。