Java的异常处理本质上是抛出异常(创建异常对象,交给运行系统处理)和捕获异常。—— 针对可恢复异常
调用栈:main –> method with an exception handler –> method without an exception handler –> method where error occurred
当错误发生时(异常抛出),会被反向传递至类型匹配的 exception handler 进行处理(捕获异常), 无法捕获将终止程序
使用异常处理的好处:将正常代码与错误处理代码分离开;通过调用栈传递错误对象;对错误类型进行组织和区分
异常分类
三种类型的异常:
- 检查性异常(编译异常):最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。—— IOException, 常见编译异常有:IOException(流传输异常),SQLException(数据库操作异常)等
- 运行时异常: 这类异常在代码编写的时候不会被编译器所检测出来,是可以不需要被捕获,但是程序员也可以根据需要进行捕获抛出。Java规定,运行时异常将由Java运行时系统自动抛出,允许应用程序忽略运行时异常。—— RuntimeException,常见的RUNtimeException有:NullpointException(空指针异常),ClassCastException(类型转换异常),IndexOutOfBoundsException(数组越界异常)等。
- 错误: 是指程序无法处理的错误,由 Java 虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。例如jvm运行时出现的OutOfMemoryError以及Socket编程时出现的端口占用等程序无法处理的错误。—— Error
把人绕晕的几个名词:
- 检查性异常: 不处理编译不能通过—— 除了RuntimeException及其子类以外,其他的Exception类及其子类都属于检查异常,当程序中可能出现这类异常,要么使用try-catch语句进行捕获,要么用throws子句抛出,否则编译无法通过。
- 非检查性异常:不处理编译可以通过,如果有抛出直接抛到控制台
- 运行时异常: 就是非检查性异常
- 非运行时异常: 就是检查性异常
try-catch-fianlly
finally 关键字用来创建在 try 代码块后面执行的代码块。无论是否发生异常,finally 代码块中的代码总会被执行。在 finally 代码块中,可以运行清理类型等收尾善后性质的语句。finally 是防止资源泄露的有效工具
- try…catch…; try….finally……; try….catch…finally…
- catch块可以有多个,注意try块只能有一个,finally块是可选的。在有多个catch块的时候,是按照catch块的先后顺序进行匹配的,一旦异常类型被一个catch块匹配,则不会与后面的catch块进行匹配。
- catch 不能独立于 try 存在
- try语句可以被嵌套, 每次进入try语句,异常的前后关系都会被推入堆栈
- 在 try/catch 后面添加 finally 块并非强制性要求的。
- try 代码后不能既没 catch 块也没 finally 块。
- try, catch, finally 块之间不能添加任何代码
- try…catch…. 捕获异常时,大的异常(Exception类)放在下方,小的异常放在上方,否则,在异常捕获时,小的异常将不能被捕获,因为全在大的异常类中捕获到。
一个典型的例子:
1 | try{ |
以上代码会返回2,catch中的return语句会被跳过,所以注意千万不要在finally块中使用return
但是也有特殊:finally不一定被执行,例如 catch 块中有退出系统的语句 System.exit(-1); finally就不会被执行
throw跟throws的区别
如果一个方法没有捕获到一个检查性异常,那么该方法必须使用 throws 关键字来声明。throws 关键字放在方法签名的尾部。
1 | public void test() throws Exception { |
throws表示一个方法声明可能抛出一个异常,throw表示此处抛出一个已定义的异常(可以是自定义需继承Exception,也可以是java自己给出的异常类)。用throw手动抛出一个异常对象
throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常对象。两者都是消极处理异常的方式,只是抛出或者可能抛出异常,但是不会由方法去处理异常,真正的处理异常由此方法的上层调用处理。
如果是不受检查异常(unchecked exception),即Error、RuntimeException或它们的子类,那么可以不使用throws关键字来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出。
必须声明方法可抛出的任何检查异常(checked exception)。即如果一个方法可能出现受可查异常,要么用try-catch语句捕获,要么用throws子句声明将它抛出,否则会导致编译错误
自定义异常
在 Java 中你可以自定义异常。
- 所有异常都必须是 Throwable 的子类。
- 如果希望写一个检查性异常类,则需要继承 Exception 类。—— 一般情况下不自定义检查异常。
- 如果你想写一个运行时异常类,那么需要继承 RuntimeException 类
自定义异常并使用:
1 | class MyException extends Exception { |
有些程序员认为检查异常是程序里的瑕疵,试图通过非检查异常来绕过,并不推荐这种做法。
方法覆盖
在当前方法被覆盖时,覆盖他的方法必须抛出相同的异常或异常的子类
- 父类的方法没有声明异常,子类在重写该方法的时候不能声明异常;
- 如果父类的方法声明一个异常exception1,则子类在重写该方法的时候声明的异常不能是exception1的父类;
- 如果父类的方法声明的异常类型只有非运行时异常(运行时异常),则子类在重写该方法的时候声明的异常也只能有非运行时异常(运行时异常),不能含有运行时异常(非运行时异常