程序在运行时难免会存在一些意外的情况的发生, 只有正确的处理好意外情况, 才能保证程序的可靠. Java 提供了一套相对完善的异常处理机制, 可以通过使用少量的代码达到可靠的效果. Java 语言的设计者根据不同的异常情况分成了 Error 和 Exception 两类, 该两类都继承了 Throwable 类, 在 Java 中只有 Throwable 类能够抛出和捕获, 因此它为 Java 异常处理机制的基础.
Error
Errror 类描述了 Java 运行时系统的内部错误和资源耗尽错误, 绝大部分的 Error 都会导致程序处于非正常的, 不可恢复的状态, 因为是非正常且不可恢复的, 因此程序不应该抛出这种类型的错误. 如果出现了这样的错误, 除了通告给用户, 并尽力使程序安全地终止外, 再也无能为力了. 但这种情况很少出现, 常见的 Error 如: OutOfMemoryError, StackOverFlowError 等.
Exception
Java 语言规范将派生于 Error 类或 RuntimeException 类的所有异常称为 unchecked 异常, 所有其他的异常称为 checked 异常.
unchecked 异常
在 Exception 结构下, uncheckn 异常继承于 RuntimeException, 也就是常说的运行时异常, 如 NullPointException,ArrayIndexOutOfBoundsException 和 illegalArgumentException 等异常, 该类异常在编译期不强制要求捕捉, 通常是因为程序逻辑错误造成的, 因此如果出现了该类异常, 多半是你自己的问题, 需要修改程序避免这类异常.
PS:RuntimeException 容易让人误解, 事实上所有的异常都是发生在运行期.
checked 异常
与 unchecked 异常的差别在于, 该类异常在编译期就强制要求抛出或捕获, 如 IOException,InterruptedException 等, 需要使用 try...catch...finally 块或者 try-with-resources 等进行处理, 或者通过 throw 直接向上层抛出异常.
Exception 实践
仅捕获有必要的代码
由于 try-catch 块会产生额外的性能开销, 会影响 JVM 对代码进行优化, 因此建议仅捕获有必要的代码段, 尽量不要一个大的 try 包住整段的代码. 同时, 也意味着不要使用异常来控制代码的流程, 远比使用 if/else 等控制语句要低效.
捕获特定异常
尽量不要捕获 Exception 这样的通用异常, 而是应该捕获特地的异常, 如在上述中使用 IOException. 直接捕获 Exception 会降低程序的可读性, 并且我们需要保证程序不会捕获到我们不希望捕获的异常, 比如, 我们更希望 RuntimeException 被扩散出来, 而不是被捕获.
不要忽略异常
; 有时候我们会认为某段代码根本不会发生异常, 而对异常进行忽略(在 catch 块中不做任何操作). 如果我们不把异常抛出来或者输出到日志之类, 那么一旦异常发生, 程序可能在后续代码以不可控的方式结束, 没人能够轻易判断是哪里以及什么原因产生了异常.
输出异常信息到日志中
- try {
- ...
- OutputStream os= new FileOutputStream(file);
- os.write(byteArray);
- } catch (IOException e) {
- e.printStackTrace();
- }
在实验代码中, 我们经常直接输出异常信息到标准错误 (STERR) 中, 但是在稍微复杂的环境中, 这不是一个合适的选择, 尤其是在分布式的系统中, 你根本无法判断信息输出到了哪里. 所以最好使用日志产品记录错误信息.
考虑将异常上抛
我们有时候会遇到捕获异常后不知道如何处理的情况, 那么与其捕获异常, 不如将异常直接抛出或者捕获后经过一定处理, 构建一个新的异常抛出. 往往在更高层面, 因为有了清晰的业务逻辑, 会更清楚合适的处理方式.
注意 try...finally 块中的 return
- public static void main(String[] args) {
- System.out.println(getValue());
- }
- public static int getValue() {
- int i = 1;
- try {
- return i;
- } finally {
- i ++;
- retutn i;
- }
- }
在 Java 中, 在 try 块中执行 return 语句前会执行 finally 块, 如果在 finally 中也有一个 return 语句, 那么 try 语句块中的 return 将不会被执行. 如上述中结果将返回 2.
- public static void main(String[] args) {
- System.out.println(getValue());
- }
- public static int getValue() {
- int i = 1;
- try {
- return i;
- } finally {
- i ++;
- }
- }
再看这段代码, 按照之前的逻辑, 变量 i 在返回前先调用了 finally 块, i 会变为 2, 但实际上结果任然为 1. 实际上, Java 虚拟机会把 finally 语句块作为 subroutine(子程序)直接插入到 try 语句块或者 catch 语句块的控制转移语句之前. 但是, 还有另外一个不可忽视的因素, 那就是在执行 subroutine(也就是 finally 语句块)之前, try 或者 catch 语句块会保留其返回值到本地变量表 (Local Variable Table) 中. 待 subroutine 执行完毕之后, 再恢复保留的返回值到操作数栈中, 然后通过 return 或者 throw 语句将其返回给该方法的调用者. 更加的详细内容请查看 Java 关键字 Finally 执行与 break, continue, return 等关键字的关系 https://cloud.tencent.com/developer/article/1065214
来源: http://www.jianshu.com/p/716f00859848