Throwable 是整个 Java 异常体系的顶层父类,它有两个子类,分别是 Error 和 Exception,Error 是程序无法处理的系统错误(系统崩溃、JVM 错误),编译器不做检查;Exception 是程序可以处理的异常,捕获后可能恢复。
Exception 又分为 RuntimeException 和非 RuntimeException(也称为 Checked Exception),RuntimeException 是不可预知的,程序应当自行避免;非 RuntimeException 是可预知的,是从编译器校验的异常。
从责任角度看,Error 属于 JVM 需要负担的责任,RuntimeException 属于程序应该负担的责任,非 RuntimeException 是 Java 编译器应该负担的责任。
Java 异常处理主要依赖于 try、catch、finally、throw、throws 五个关键字。
1、异常处理语法结构
try {
int i = 10 / 0; // 会抛出异常
// 自行抛出异常,throw 抛出的是一个异常实例,每次只能抛出一个
// throw 抛出 Checked Exception: throw 语句要放在 try 块里或者一个带 throws 声明抛出的方法中
// throw 抛出 RuntimeException: 自由放
// throw new RuntimeException("");
} catch(ArithmeticException e) { // 捕获异常
// 捕获 ArithmeticException
log.error("类名.方法名 error", e); // 抛出的异常打印在日志中
} catch(Exception e) {
// 捕获 Exception
log.error("类名.方法名 error", e);
// throw new RuntimeException(""); // 再次抛出异常
// System.exit(1); // 退出虚拟机, finally块失去执行的机会
} finally { // 资源回收块
}
- try 块是必需的,catch 块和 finally 块至少出现其中一个;
- catch 块按顺序进行捕获,如果被捕获了,则后面的 catch 块不会被执行,如果找不到可以捕获的 catch 块,则继续向上抛出异常,如果到了最外层都无法捕获,则 Java 运行时环境终止,程序在此退出;
- 如果 finally 中有 return 语句,则 try、catch 中的 return 失效,总是执行 finally 中有 return;
- finally 中的语句(如果有 return,则表示 return 前的语句)会在 try、catch 中的 return 之前执行。
看下执行顺序的范例:
try {
logger.info("hello"); // 第一步
return true;
} catch(Exception e) {
} finally {
logger.info("world"); // 第二步
return false; // 第三步
}
try {
logger.info("hello"); // 第一步
return true; // 第三步
} catch(Exception e) {
} finally {
logger.info("world"); // 第二步
}
try {
int i = 10 / 0; // 第一步
return true;
} catch(Exception e) {
logger.error("类名.方法名 error", e); // 第二步
return false; // 第四步
} finally {
logger.info("world"); // 第三步
}
2、使用 throws 声明抛出异常:
当前方法不知如何处理出现的异常,抛由上一级调用者处理;main 方法抛出的异常则交由 JVM 处理(打印异常的跟踪栈信息并中止程序运行)。
// 此处异常将交由JVM处理
public static void main(String[] args) throws IOException{
}
注意:子类方法抛出的异常范围应小于等于父类,父类异常的 catch 块都应该排在子类异常 catch 块的后面。
3、自定义业务异常类:
在用户看来,应用系统发生的所有异常都是应用系统内部的异常。所以需要设计一个通用的继承自 RuntimeException 的异常来统一处理:
public class BizException extends RuntimeException {
private static final long serialVersionUID = 1L;
public BizException() {
super();
}
public BizException(String message) {
super(message);
}
public BizException(String message, Throwable cause) {
super(message, cause);
}
public BizException(Throwable cause) {
super(cause);
}
}
其余异常都统一转译为上述异常 BizException,在 cache 之后,抛出上述异常的子类,并提供足以定位问题的信息,由控制器层接收 BizException 做统一处理。
4、异常链
分层结构:表现层(用户界面)–>中间层(业务逻辑)–>持久层(保存数据)
上层功能的实现严格依赖于下层的API,不会跨层访问
异常转译:捕获异常后抛出一个新的业务异常(包含对用户的提示信息)
这种把捕获的一个异常接着抛给另一个异常,并把原始异常信息保存下来是一种典型的链式处理(23种设计模式之一:职责链模式),也被称为“异常链”。
5、异常跟踪栈
当产生异常的时候,异常从方法1触发,传到方法2,再传到方法3…最后传到main方法,在main方法终止,这个过程就是Java的异常跟踪栈。
方法调用栈:面向对象的应用运行时,会发生一系列方法调用,从而形成“方法调用栈”。
6、JDK1.7 增强的功能
多异常捕获:
try {
} catch(IndexOutOfBoundsException|NumberFormatException|NullPointerException e) {
}
增强了try 块:
允许在 try 关键字后加 ( ) ,圆括号可声明、初始化一个或多个资源,具体指那些必须在程序结束时显式关闭的资源,比如数据库连接、网络连接、流等,这些资源实现类必须实现 AutoCloseable 或 Closeable,实现 close() 方法,try 语句在该语句结束的时候自动关闭这些资源。
自动关闭资源的 try 语句相当于包含了隐式的 finally 块(用于关闭资源),故这个 try 语句可以既没有 catch 块,也没有 finally 块。
try (
// 声明、初始化可关闭资源, try语句会自动关闭
BufferedReader br = new BufferedReader(new FileReader("Test.java"));
) {
// 使用资源
System.out.println(br.readLine());
} catch (Exception e) {
} finally {
}
7、Java 异常处理使用建议
- 不要过度使用异常;
- try 块避免庞大;
- 避免 finally 块中使用 return 或 throw 等导致方法终止的语句;
- 避免使用 CatchAll 语句。
CatchAll 语句:
try (
} catch(Throwable t) {
}
8、try-catch 存在性能问题?
- 通过对比编译后的字节码,可以发现 try-catch 块会影响 JVM 指令重排序的优化;
- 遇到异常后,便会实例化一个 Exception,并且会对 Exception 对象实例保存栈快照等信息,开销较大。
所以说在不发生异常的情况下,try-catch 块对性能的影响非常小,遇到异常后开销较大。