Java第9章 异常处理

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
2
3
4
5
6
7
8
9
try{
//待捕获代码
}catch(Exception e){
System.out.println("catch is begin");
return 1
}finally{
System.out.println("finally is begin");
return 2 ;
}

以上代码会返回2,catch中的return语句会被跳过,所以注意千万不要在finally块中使用return

但是也有特殊:finally不一定被执行,例如 catch 块中有退出系统的语句 System.exit(-1); finally就不会被执行

throw跟throws的区别

如果一个方法没有捕获到一个检查性异常,那么该方法必须使用 throws 关键字来声明。throws 关键字放在方法签名的尾部。

1
2
3
public void test() throws Exception {
throw new Exception();
}

throws表示一个方法声明可能抛出一个异常,throw表示此处抛出一个已定义的异常(可以是自定义需继承Exception,也可以是java自己给出的异常类)。用throw手动抛出一个异常对象

throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常对象。两者都是消极处理异常的方式,只是抛出或者可能抛出异常,但是不会由方法去处理异常,真正的处理异常由此方法的上层调用处理。   

如果是不受检查异常(unchecked exception),即Error、RuntimeException或它们的子类,那么可以不使用throws关键字来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出。
必须声明方法可抛出的任何检查异常(checked exception)。即如果一个方法可能出现受可查异常,要么用try-catch语句捕获,要么用throws子句声明将它抛出,否则会导致编译错误

自定义异常

在 Java 中你可以自定义异常。

  • 所有异常都必须是 Throwable 的子类。
  • 如果希望写一个检查性异常类,则需要继承 Exception 类。—— 一般情况下不自定义检查异常。
  • 如果你想写一个运行时异常类,那么需要继承 RuntimeException 类

自定义异常并使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class MyException extends Exception {
private int detail;
MyException(int a){
detail = a;
}
public String toString(){
return "MyException ["+ detail + "]";
}
}
public class TestMyException{
static void compute(int a) throws MyException{
System.out.println("Called compute(" + a + ")");
if(a > 10){
throw new MyException(a);
}
System.out.println("Normal exit!");
}
public static void main(String [] args){
try{
compute(1);
compute(20);
}catch(MyException me){
System.out.println("Caught " + me);
}
}
}

有些程序员认为检查异常是程序里的瑕疵,试图通过非检查异常来绕过,并不推荐这种做法。

方法覆盖

在当前方法被覆盖时,覆盖他的方法必须抛出相同的异常或异常的子类

  1. 父类的方法没有声明异常,子类在重写该方法的时候不能声明异常;
  2. 如果父类的方法声明一个异常exception1,则子类在重写该方法的时候声明的异常不能是exception1的父类;
  3. 如果父类的方法声明的异常类型只有非运行时异常(运行时异常),则子类在重写该方法的时候声明的异常也只能有非运行时异常(运行时异常),不能含有运行时异常(非运行时异常

一篇详细的文章