《Java编程思想》读书笔记 第十二章 通过异常处理

《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;
	}
    }
}

运行程序后,即是抛出了异常,它也不会产生任何输出

编辑于 2017-03-22 21:39