一般为底层的不可恢复的类,一般此类错误都比较严重,JVM将终止其运行的线程;
程序本身可以捕获并且可以预处理的异常,例如捕获或者抛出;
CheckException
受检查异常,编译阶段必须处理;
编写异常类时直接继承Exception
让它成为一个受检异常
RuntimeException
运行时异常,可不用捕获,其实Exception都是受检异常,RuntimeException反而可以看成一种特例
运行时异常场景
throws
允许你对某个受检异常不进行处理,而是抛出去交给上层调用方处理
throws
,调用方也必须要处理(或者继续抛出)错误的栈轨迹,栈顶元素是抛出异常的地方
public class ExceptionStack { public static void main(String[] args) { h(); } private static void f() { try{ throw new RuntimeException(); } catch (Exception e) { for (StackTraceElement element : e.getStackTrace()) { System.out.println(element.getMethodName()); } } } private static void g() { f(); } private static void h() { g(); } }
测试结果
f g h main
catch(Exception e) { throw e; }
堆栈信息不会记录新的调用点,重新抛出的地方不会成为栈顶元素
即如果直接抛出原来的异常对象,错误的堆栈信息不会改变
public class ExceptionStack2 { public static void main(String[] args) { h(); } private static void f() { throw new RuntimeException(); } private static void g() { try { f(); } catch (Exception e) { System.out.println("g() : 重新抛出异常"); throw e; } } private static void h() { try { g(); } catch (Exception e) { for (StackTraceElement element : e.getStackTrace()) { System.out.println(element.getMethodName()); } } } }
测试结果
g() : 重新抛出异常 f g h main
如果重新构建新的异常,那么栈顶元素就是抛出的地方了,就跟正常抛出一样
public static void main(String[] args) { h(); } private static void f() { throw new RuntimeException(); } private static void g() { try { f(); } catch (Exception e) { System.out.println("g() : 重新构建异常抛出"); throw new RuntimeException("g() : exception"); } } private static void h() { try { g(); } catch (Exception e) { for (StackTraceElement element : e.getStackTrace()) { System.out.println(element.getMethodName()); } } }
测试结果
从测试结果可以看出,g()这个方法抛出的异常不会保留f()方法调用它的轨迹了
g() : 重新构建异常抛出 g h main
上面提到重新构建异常抛出时无法得到完整的调用链了,那么我们如何得到上一个异常发生的信息呢?
在我们重新抛出异常的时候希望保存原来的异常对象信息,JDK1.4
之后我们就可以在构造时传入一个cause
异常对象,保留上一个异常对象的信息
在堆栈打印时会通过 Cause by
打印上一个异常对象的栈轨迹
public static void main(String[] args) { try { h(); } catch (Exception e){ e.printStackTrace(); } } private static void f() { throw new RuntimeException("f(): exception"); } private static void g() { try { f(); } catch (Exception e) { throw new RuntimeException("g(): exception",e); } } private static void h() { try { g(); } catch (Exception e) { throw new RuntimeException("h(): exception",e); } }
测试结果
java.lang.RuntimeException: h(): exception at example.exception.usage.demo2.ExceptionChain.h(ExceptionChain.java:28) at example.exception.usage.demo2.ExceptionChain.main(ExceptionChain.java:6) Caused by: java.lang.RuntimeException: g(): exception at example.exception.usage.demo2.ExceptionChain.g(ExceptionChain.java:20) at example.exception.usage.demo2.ExceptionChain.h(ExceptionChain.java:26) ... 1 more Caused by: java.lang.RuntimeException: f(): exception at example.exception.usage.demo2.ExceptionChain.f(ExceptionChain.java:13) at example.exception.usage.demo2.ExceptionChain.g(ExceptionChain.java:18) ... 2 more
某些异常的构造函数中可能没有cause(Throwable)
这种参数(例如FileNotFoundException
),但是我们还可以用initCause来保存上一个异常对象
这里仅作演示,RuntimeException
是可以直接通过构造的方式保存异常链的
private static void g() throws Exception { try { f(); } catch (Exception e) { Exception exception = new RuntimeException("g(): exception"); exception.initCause(e); throw exception; } }
finally
即try/try-catch
后一定会执行的语句
public static void main(String[] args) { System.out.println("get(): " + get()); } private static int get() { try { return 1; } finally { System.out.println("就算try return了, finally也会执行"); } }
测试结果
就算try return了, finally也会执行 get(): 2
finally
的return
,所以最好别这么做public static void main(String[] args) { System.out.println("get(): " + retInfinally()); } private static int retInfinally() { try { return 1; } finally { return 2; } }
测试结果
get(): 2
finally
中抛出的异常会覆盖try
或catch
中抛出的异常finally
中抛出异常,但你有可能在finally
中调用其他方法,如果抛出了调用方法时抛出了异常,则会覆盖原有异常public static void main(String[] args) { System.out.println("throwInfinally(): " + throwInfinally()); } static class ImportantException extends Exception { } private static int throwInfinally() { try { throw new ImportantException(); } finally { throw new RuntimeException("finally中抛出:运行时异常"); } }
运行结果
Exception in thread "main" java.lang.RuntimeException: finally:运行时异常 at example.exception.usage.demo3.FinallyTest2.throwInfinally(FinallyTest2.java:25) at example.exception.usage.demo3.FinallyTest2.main(FinallyTest2.java:7)
finally
中进行return
也会吞掉异常public static void main(String[] args) { System.out.println("get(): " + retInfinally()); } static class ImportantException extends Exception { } private static int retInfinally() { try { throw new ImportantException(); } finally { return 2; } }
运行结果
get(): 2
finally
常被用来做资源清理,但是资源清理的前提是资源确实被初始化了,那如果资源初始化就失败了呢?那么finally
中就不应该进行资源清理,比较好的方式是通过嵌套try语句,这里就直接用《OnJava8》中的demo了
public class Cleanup { public static void main(String[] args) { try { InputFile in = new InputFile("Cleanup.java"); try { String s; int i = 1; while((s = in.getLine()) != null) ; // Perform line-by-line processing here... } catch(Exception e) { System.out.println("Caught Exception in main"); e.printStackTrace(System.out); } finally { in.dispose(); } } catch(Exception e) { System.out.println( "InputFile construction failed"); } } }
从上面的例子中我们可以发现资源的初始化与释放操作手动编写比较复杂,所以在从Java7
开始提供了 try-with-resources
基本原理就是通过实现AutoCloseable
接口,完成自己的资源释放操作,所以只要是实现了这个接口的类我们都可以通过try-with-resources
在try语句中进行资源的初始化
这里还是直接使用《OnJava8》中的例子:
public static void main(String[] args) { try( InputStream in = new FileInputStream( new File("TryWithResources.java")) ) { int contents = in.read(); // Process contents } catch(IOException e) { // Handle the error } }
需要构建多个资源对象时可以用分号分割,注意顺序,后声明资源的会先关闭
这个就是我们常常背的重写要求之一了,即重写的方法不能抛出比父类范围更广的异常
这样做的意义就是保证接口的通用性:
被第一个匹配的catch捕获,例如说你不能先catch一个Exception,再catch它的子类,编译器会报错,因为后面的catch语句永远匹配不到
try { // do something } catch (Exception e){ e.printStackTrace(); } catch (RuntimeException e){ // 这样是不行滴 }
这里直接贴一下《OnJava8》中的异常指南
应该在下列情况下使用异常:
- 尽可能使用 try-with-resource。
- 在恰当的级别处理问题。(在知道该如何处理的情况下才捕获异常。)
- 解决问题并且重新调用产生异常的方法。
- 进行少许修补,然后绕过异常发生的地方继续执行。
- 用别的数据进行计算,以代替方法预计会返回的值。
- 把当前运行环境下能做的事情尽量做完,然后把相同的异常重抛到更高层。
- 把当前运行环境下能做的事情尽量做完,然后把不同的异常抛到更高层。
- 终止程序。
- 进行简化。(如果你的异常模式使问题变得太复杂,那用起来会非常痛苦也很烦人。)
- 让类库和程序更安全。(这既是在为调试做短期投资,也是在为程序的健壮性做长期投资。
------ 《OnJava8》
异常日志一版记录哪些信息?
首先就是看下异常时间,即什么时候发生的异常,然后通过异常类型可以判断是哪一块有问题,通过异常信息和业务参数进一步确定异常,如果还解决不了,那就得通过异常位置看代码
关于异常处理其实还有很多内容值得学习和研究,例如:
Java中处理异常的一些场景,什么时候应该抛出原有异常,什么时候应该重新构建新的异常?
其他语言是怎么处理异常的?
极客上有几篇专栏是与异常处理相关的,等看完那几篇文章研究一下之后再来进行记录
异常处理:别让自己在出问题的时候变为瞎子
程序中的错误处理:错误返回码和异常捕捉