《Java编程思想》读书笔记 第十二章 通过异常处理
常见的try...catch...finally语句
int a = 1;
int b = 0;
try {
System.out.println("a/b = " + a / b);
} catch (Exception) {
System.out.println("出现异常");
} finally {
System.out.println("程序正常结束");
}
Java异常
Error(错误):是程序无法处理的错误,表示运行应用程序中较严重的错误
Exception(异常):是程序本身可以处理的异常
异常处理机制
抛出异常:当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息;运行时系统负责寻找处置异常的代码并执行
捕获异常:在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器;潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合;当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适的异常处理器;运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行;当运行时系统便利调用栈而未找到合适的异常处理器,则运行时系统终止,同时意味着Java程序终止
异常总是先被抛出,然后被捕获的
- 在语句中,可以使用throw语句抛出异常;从方法中抛出的任何异常都必须使用throws语句
- 捕获异常通过try-catch语句或try-catch-finally语句实现
try {
...//可能会抛出异常的程序代码
} catch (Exception1 e1) {
...
} catch (Exception2 e2) {
...
} catch (Exception e) {
...//必须将Exception异常放在最后
} finally {
...//无论是否异常发生,都将执行这部分语句
}
如果catch语句中存在return,则finally之后的语句将得不到执行
throw与throws的差别
throw是语句抛出一个Throwable类型的异常,总是出现在函数体中;程序会在throw语句之后立即终止
如果一个方法可能会出现异常,但没有能力处理这种异常,可以在方法声明处用throws子句来声明抛出异常;throws语句用在方法定义时声明该方法要抛出的异常类型,多个异常可使用逗号分割
methodName() throws Exception1, Exception2, Exception3, ... {
...
}
例如:
import java.lang.Exception;
public class TestException {
public int div(int x, int y) throws MyException {
if (y == 0) {
throw new MyException("除数不能为0");
}
return (int)(x/y);
}
public static void main(String[] args) {
int x = 1;
int y = 0;
try {
int result = div(x, y);
} catch (MyException e) {
System.out.println(e.getMessage());
}
}
}
//自定义异常类
class MyException extends Exception {
String message;
public MyException(String ErrorMessage) {
message = ErrorMessage;
}
public String getMessage() {
return message;
}
}
异常链
在捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,这就是异常链
现在所有Throwable的子类在构造器中都可以接受一个cause(原因)对象作为参数,这个cause就用来表示原始异常,这样通过把原始异常传递给新的异常,使得即使在当前位置创建并抛出新的异常,也能通过这个异常链追踪到异常最初发生的位置
Throwable的子类中,只有三种基本的异常类提供了带cause参数的构造器:Error,Exception,RuntimeException;如果要把其他类型的异常链连接起来,应该使用initCause()方法而不是构造器
class MyException1 extends Exception {
}
class MyException2 extends Exception {
MyException2(Throwable throwable) {
super(throwable);
}
MyException2() {
super();//调用父类的构造方法,目的是为了传递一个cause进来
}
}
class ExceptionTest {
public void f() throws MyException2 {
try {
g();
} catch (MyException1 e) {
e.printStackTrace();
throw new MyException2(e);
}
}
public void g() throws MyException1 {
throw new MyException1();
}
}
public class ExceptionsLink {
public static void main(String[] args) {
ExceptionTest et = new ExceptionTest();
try {
et.f();
} catch (MyException2 e) {
e.printStackTrace();
}
}
}
MyException1
at ExceptionTest.g(ExceptionsLink.java:24)
at ExceptionTest.f(ExceptionsLink.java:17)
at ExceptionsLink.main(ExceptionsLink.java:33)
MyException2: MyException1
at ExceptionTest.f(ExceptionsLink.java:20)
at ExceptionsLink.main(ExceptionsLink.java:33)
Caused by: MyException1
at ExceptionTest.g(ExceptionsLink.java:24)
at ExceptionTest.f(ExceptionsLink.java:17)
... 1 more
在方法f()中调用g()方法,而g()方法可能会抛出一个MyException1类异常,在catch这个异常时,又会抛出一个MyException2类异常
将MyException1作为cause传入MyException2的有参构造函数里,这样就能获取到MyException1的信息了
异常丢失
try-catch-finally有个漏洞就是异常缺失,例如三个try-catch嵌套在一起,内部的两个try-catch就可以省略catch,直接try-finally
class MyException11 extends Exception {
public String toString() {
return "MyException11";
}
}
class MyException22 extends Exception {
public String toString() {
return "MyException22";
}
}
public class ExceptionLost {
void f() throws MyException11 {
throw new MyException11();
}
void g() throws MyException22 {
throw new MyException22();
}
public static void main(String[] args) {
try {
ExceptionLost el = new ExceptionLost();
try {
el.f();
} finally {
el.g();
}
} catch (Exception e) {
System.out.println(e);
}
}
}
MyException22
由输出可知,MyException1类异常不见了,被finally子句中的MyException2类异常所取代,这是相当严重的缺陷
如果finally语句抛出异常,那么这个异常会向上传递,而之前try语句块中的那个异常就丢失了
一种更加简单的丢失异常的方式是从finally子句中返回
public class ExceptionSilencer {
public static void main(String[] args) {
try {
throw new RuntimeException();
} finally {
return;
}
}
}
运行程序后,即是抛出了异常,它也不会产生任何输出