15 异常
- Java的基本哲学:写得不好的代码无法运行
15.1 概念
- C 语言和其他较早的语言往往有多种错误处理机制,这类机制通常是通过约定建立的,而不是作为编程语言的一部分。
- 异常可以降低错误处理代码的复杂性
- 如果没有异常,就必须检查某个特定的错误并加以处理,可能要在程序多个地方进行
- 而异常可以确保有人会捕捉他们,并且只需在一个地方进行处理
15.2 基本的异常
- 异常情况:指组织当前方法或作用域继续执行的问题.
- 需要区分普通问题和异常
- 普通问题:当前上下文有足够的信息解决问题
- 异常:当前上下文没有足够的信息处理问题,因此需要跳出当前上下文,将问题委托给更上层的上下文
- 当抛出一个异常时,会发生以下几件事
- 异常对象创建,使用new创建,放在堆上
- 当前执行路径停止,指向这个异常对象的引用被从上下文中推出
- 异常处理机制接管控制,寻找异常处理程序
异常参数
- 和Java其它对象一样,使用new在堆上创建异常,其会分配存储空间并调用构造器
- 所有标准异常类有连个构造器,一个无参构造器,第二个接收一个String参数,用于放置在异常中放置的相关信息
if(t == null)
throw new NullPointerException("t = null");
- 关键词throw有意思
- 使用new创建异常对象后,使用throw将其返回,这种返回类似于return,但是不会返回任何值,而是指示程序退出当前执行路径
- 此外可以抛出任何类型的 Throwable(异常类型的根类),通常错误信息就包含在异常类型的名字中
15.3 捕捉异常
- 被守护区域:一段可能产生异常的代码,后面跟着处理异常的代码
try 块
- try块包含了异常可能发生的代码,这样在代码抛出异常后,异常处理机制就能捕获它
- 用类似C这样不支持异常处理的语言编写的库,需要依赖临时的机制报告错误. 有了异常处理,可以把所有的内容放在一个try块中,并在一个地方捕获所有异常,意味着代码更容易编写和阅读,因为代码的正确执行目的不会被错误检查所混淆
try {
// 可能会产生异常的代码
}
异常处理程序
- 异常处理程序紧跟在 try 块之后,用关键字 catch 来表示。
try {
// 可能会产生异常的代码
} catch(Type1 id1) {
// 处理 Type1 类型的异常
} catch(Type2 id2) {
// 处理 Type2 类型的异常
} catch(Type3 id3) {
// 处理 Type3 类型的异常
}
- 每个catch子句(异常处理程序)像一个小方法,接收且只接收一个特定类型的参数. 标识符(id1,id2)可以在处理程序中使用,它们引用了抛出异常对象的引用
- 异常处理程序需要紧跟在try块后,一个异常抛出后,异常处理机制和寻找参数和异常类型相匹配的一个处理程序,然后进入catch子句,我们就认为这个异常被处理
终止与恢复
-
在异常处理理论中,有两种基本模型
- 终止模型 termination:异常处理程序不试图修复错误,而是终止程序运行. Java支持终止模型
- 恢复模型 resumption:异常处理程序负责纠正错误,并重新尝试调用错误产生的方法. 支持恢复模型意味着我们仍然希望在处理完异常后继续执行
-
如果想在Java中达到恢复模型的效果,可以把try块放在一个while循环中,不断重新进入try块直到结果令人满意
-
从历史上看,最终仍是使用终止模型,因为恢复模型将导致耦合,使得代码难以编写和维护
15.4 创建自己的异常类
- 通常,异常类都是继承自Exception类,或者继承自Exception的某个子类
- 一个异常最重要的是其类名
class SimpleException extends Exception {}
public class InheritingExceptions {
public void f() throws SimpleException {
System.out.println(
"Throw SimpleException from f()");
throw new SimpleException();
}
public static void main(String[] args) {
InheritingExceptions sed =
new InheritingExceptions();
try {
sed.f();
} catch(SimpleException e) {
System.out.println("Caught it!");
}
}
}
/* Output:
Throw SimpleException from f()
Caught it!
*/
- 也可以创建一个接收String参数的异常类
class MyException extends Exception {
MyException() {}
MyException(String msg) { super(msg); }//调用父类的构造器,Exception的构造器接收一个String参数,同时Exception继承自Throwable
}
public class FullConstructors {
public static void f() throws MyException {
System.out.println("Throwing MyException from f()");
throw new MyException();
}
public static void g() throws MyException {
System.out.println("Throwing MyException from g()");
throw new MyException("Originated in g()");
}
public static void main(String[] args) {
try {
f();
} catch(MyException e) {
e.printStackTrace(System.out);//Throwable的printStackTrace()方法,打印异常栈轨迹,这里将其输出到System.out,如果使用无参版本,则会将其输出到System.err
}
try {
g();
} catch(MyException e) {
e.printStackTrace(System.out);
}
}
}
/* Output:
Throwing MyException from f()
MyException
at FullConstructors.f(FullConstructors.java:11)
at
FullConstructors.main(FullConstructors.java:19)
Throwing MyException from g()
MyException: Originated in g()
at FullConstructors.g(FullConstructors.java:15)
at
FullConstructors.main(FullConstructors.java:24)
*/
异常和日志记录
- 可以使用java.util.logging工具将输出记录到日志中
略
15.5 异常说明
- 异常声明exception specificaton 为方法声明的组成部分,出现在参数列表之后. 可以语法礼貌地告诉程序员这个方法会抛出的异常
void f() throws TooBig, TooSmall, DivZero { // ...
- 而以下写法意味着这个方法不会抛出异常(除了从RuntimeExecption继承而来的异常,这样的异常可以从任何地方抛出而不要异常说明)
void f() { // ...
- Java保证在编译时提供一定程度的异常正确性,即如果方法中出现了异常,要么处理这个异常,要么使用异常说明指明这个异常可能会被抛出
- 这种在编译时被检查并强制实施的异常叫做检查型异常(checked exception)
15.6 捕捉任何异常
- 通过捕捉异常类型的基类Exception,可以捕捉任何异常. 使用时需要放到处理列表的最后,避免在其他异常处理程序之前捕获了异常
catch(Exception e) {
System.out.println("Caught an exception");
}
- Exception类是所有对程序员很重要的异常类的基类,因此我们不能从他得到太多信息,我们可以通过调用其基类Throwable的方法来获取信息
- getMessage()方法返回异常对象的详细信息
- getLocalizedMessage()方法返回异常对象的本地化信息
- toString()方法返回异常对象的类型和详细信息
- 以下方法打印Throwable的调用栈轨迹,调用栈显示了异常抛出的地方,以及异常传播的路径
- printStackTrace(),打印到标准错误流
- printStackTrace(PrintStream),打印到指定流
- printStackTrace(PrintWriter),打印到指定流