异常概述
异常指的是程序在执行的过程中,出现的非正常情况,如果不处理最终会导致JVM的非正常停止。
在Java中,使用不同的类来表示不同的异常(正所谓万物皆对象,因此异常也使用类来表示)。一旦程序出现某种异常就创建该异常类型的对象,并且抛出。然后,程序如果捕捉到这个异常对象,那么就会进行处理;如果没有捕捉到这个异常对象,那么就会导致程序非正常停止。
因此,程序员在编写代码的时候,就要充分考虑到各种可能发生的异常和错误,极力进行避免和预防。对于实在无法避免的,要编写相应的代码进行异常的检测、异常的处理,从而增强代码的健壮性。
异常的体系结构
Throwable
java.lang.Throwable 类是Java程序执行过程中发生的异常事件对应类的根父类。
Throwable 类存在两个子类,分别是:Error、Exception。
Error
Error 类表示的异常是:Java虚拟机无法解决的严重问题。如,JVM系统内部错误、资源耗尽等情况。一般是不会编写针对性的代码进行处理,毕竟编写代码也不好处理。
StatckOverflowError(栈内存溢出)和OutOfMemoryError(堆内存溢出,简称OOM)是Error较为常见的场景,一般中高级程序猿才能处理这种问题。
Exception
Exception 类表示的异常是:其他因编程错误或偶然的外在因素导致的一般性问题,需要编写针对性的代码进行处理,使程序继续运行。否则一旦发生异常,程序就会挂掉。例如:
- 空指针异常
- 试图读取不存在的文件
- 网络连接中断
- 数组角标越界
Exception 异常又分为编译时异常(在执行javac命令时出现的异常,因此也被称为受检异常)和运行时异常(在执行java命令时出现的异常,因此也被称为非受检异常)。
异常的处理方式
try-catch-finally
抛抓模型
对于抛抓模型来说,分为两个过程:一抛一抓。
对于抛过程来说,在程序执行的过程中,一旦出现异常,那就会在产生异常的代码处生成对应异常类的对象,并将此对象抛出。一旦将对象抛出,那么就不会再继续执行代码,等待抓过程的实现。
对于抓过程来说,当抛出异常时,抓过程就开始了。程序针对抛过程产生的异常对象进行捕获处理。一旦将异常处理,那么程序就会继续执行,但是如果并没有处理掉,那么程序就会终止。
基本结构
try {
// 可能产异常的代码
}
catch (异常类型1) {
// 当产生异常类型1时的处置措施
}
catch (异常类型2) {
// 当产生异常类型2时的处置措施
}
finally {
// 无论是否发生异常,都无条件执行的代码
}
对于try结构来说,其中存放的就是可能产生异常的代码。当异常出现时,就会生成对应异常类的对象并抛出,并且无论是否处理了异常,try结构中产生异常代码之后的代码都不会继续执行。值得注意的是,try结构中的变量,出了try结构就不能使用。
对于catch结构来说,其中存放的是处理某种异常类的代码。一个try结构可以对应无数个catch结构,毕竟代码可能产生的异常不止一种类型。对于try抛出的异常对象来说,catch结构就会进行匹配,一旦匹配上,就会执行catch结构中的代码,执行完毕之后,继续向下执行。值得注意的是,如果声明了多个catch结构,不同的异常类型不存在子父类的情况下,谁声明在上,谁声明在下都可以;如果多个类型满足子父类的情况,则子类必须声明在上面,父类必须声明在下边。
对于finally结构来说,其中存放的就是一定要执行的代码,也就是说,无论能否处理了异常,finally结构中的代码一定会执行(唯一例外的就是如果使用System.exit(0)来结束当前正在运行的Java虚拟机,那么就不会有任何的代码进行执行)。所以,当有一定要执行的代码时,就要把他放到finally结构中(例如在开发中,有一些资源,如输出流、输入流、数据库连接以及Socket连接等,在使用完成之后,必须有显式的关闭。否则,GC会认为这些代码还要使用,就不会进行回收利用,进而导致内存泄漏。像这样的就可以把关闭操作放在finallyt中)。
注意,try是必须存在的,catch和finally两者可以同时存在,也可以存在一个,但是不能都不存在。
catch中处理异常的方式
1. 自己编写输出语句,表示异常的出现。
2. printStackTrace(),打印异常的详细信息。(推荐)
3. getMessage(),获取发生异常的原因
开发心得
1. 对于运行时异常,通常不进行显式的处理。一旦在程序运行过程中,出现了运行时异常,那么就根据异常的提示信息修改代码即可。
2. 对于编译时异常,一定要进行处理,否则编译不通过。
throws
基本结构
public void test() throws 异常类型1, 异常类型2 ... {
// 存在编译时异常
}
对于throws这种处理异常的方式来说,其实就是在方法声明的地方抛出了异常,当A方法调用此方法时,A方法继续抛出异常,B方法调用A方法时,继续抛出异常。如果异常真正发生了,那么程序照样会终止。因此这种方式存在的一个问题就是:是否真正处理了异常?
是否真正处理了异常?
1. 从编译是否能通过的角度看,也看成是给出了异常万一出现的解决方法。而所谓的解决方案就是继续向上抛出。
2. 如果说,throws仅是将可能出现的异常抛给了此方法的调用者。调用者仍然需要考虑如何解决处理相关异常。那么从这个角度来说,就不算解决了异常。
开发中两种方式的选择
1. 资源一定要被执行:try-catch-finally。
2. 如果父类被重写的方法没有 throws 异常类型,则子类重写的方法中如果出现异常,只能考虑使用 try-catch-finally 进行处理,不能 throws。
3. 开发中,方法a中依此调用了b、c、d等方法,方法b、c、d之间是递进关系。此时,如果方法b、c、d中有异常,我们通常使用throws,而方法a中通常选择使用try-catch。
手动抛出异常
在实际开发中,如果出现不满足具体场景的代码问题,我们就有必要手动抛出一个指定类型的对象。通俗的说,就是在实际问题的解决中,我们不想出现某些数据,那此时就可以手动抛出。
结构:throw + 异常类的对象。
所谓的手动抛出,就是抛抓模型中的抛过程。只不过手动抛出,而非程序自动抛出。
自定义异常
如何自定义异常类?
1. 继承于现有的异常体系。
2. 通常提供几个重载的构造器。
3. 提供一个全局变量,声明为:static final long serialVersionUID
如何使用自定义异常类?
1. 在具体的代码中,满足指定条件的情况下,需要手动的使用throw + 自定义异常类对象
的方式,将异常类对象抛出。
2. 如果自定义异常类是非运行时异常,则必须考虑如何处理此异常类的对象(正是异常处理的两种方式)。
为什么需要自定义异常类?
更希望通过异常类的名称来判断具体出现的问题。
举例
/*** 自定义异常*/public class CustomException extends Exception{public CustomException() {super();}public CustomException(String message) {super(message);}public CustomException(Throwable cause) {super(cause);}}