java异常机制、异常栈、异常处理方式、异常链、异常丢失
1、java标准异常概述
Throwable表示任何可以作为异常被抛出的类,有两个子类Error和Exception。从这两个类的源代码中可以看出,这两个类并没有添加新的方法,Throwable提供了所以方法的实现。Error表示编译时和系统错误。Exception是可以被抛出的异常类。RuntimeException继承自Exception(如NullPointerException),表示运行时异常,JVM会自动抛出.
2、自定义异常类
自定义异常类方法:?通过继承Throwable或Exception。异常类的所有实现都是基类Throwable实现的,所以构造自定义异常类完全可以参考Exception和Error类。我们只要添加上自定义异常类的构造方法就可以了
[java]?view plaincopy?- <span?style="font-size:16px;">package?demo.others;??
- ??
- /**?
- ?*?自定义异常类方法?
- ?*?1、通过继承Throwable?
- ?*?2、通过继承Exception?
- ?*??
- ?*?@author?Touch?
- ?*/??
- public?class?MyExceptionDemo?extends?Exception?{??
- ??
- ????private?static?final?long?serialVersionUID?=?1L;??
- ??
- ????public?MyExceptionDemo()?{??
- ????????super();??
- ????}??
- ??
- ????public?MyExceptionDemo(String?message)?{??
- ????????super(message);??
- ????}??
- ??
- ????public?MyExceptionDemo(String?message,?Throwable?cause)?{??
- ????????super(message,?cause);??
- ????}??
- ??
- ????public?MyExceptionDemo(Throwable?cause)?{??
- ????????super(cause);??
- ????}??
- }??
- </span>??
?
3、异常栈及异常处理方式
可以通过try、catch来捕获异常。捕获到的异常。下面的示例演示了几种常用异常处理方式
[java]?view plaincopy?- <span?style="font-size:16px;">package?demo.others;??
- ??
- import?mine.util.exception.MyException;??
- ??
- public?class?ExceptionDemo1?{??
- ????public?void?f()?throws?MyException?{??
- ????????throw?new?MyException("自定义异常");??
- ????}??
- ??
- ????public?void?g()?throws?MyException?{??
- ????????f();??
- ????}??
- ??
- ????public??void?h()?throws?MyException??{??
- ????????try?{??
- ????????????g();??
- ????????}?catch?(MyException?e)?{??
- ????????????//1、通过获取栈轨迹中的元素数组来显示异常抛出的轨迹??
- ????????????for?(StackTraceElement?ste?:?e.getStackTrace())??
- ????????????????System.out.println(ste.getMethodName());??
- ????????????//2、直接将异常栈信息输出至标准错误流或标准输出流??
- ????????????e.printStackTrace();//输出到标准错误流??
- ????????????e.printStackTrace(System.err);??
- ????????????e.printStackTrace(System.out);??
- ????????????//3、将异常信息输出到文件中??
- ????????????//e.printStackTrace(new?PrintStream("file/exception.txt"));??
- ????????????//4、重新抛出异常,如果直接抛出那么栈路径是完整的,如果用fillInStackTrace()??
- ????????????//那么将会从这个方法(当前是h()方法)作为异常发生的原点。??
- ????????????//throw?e;??
- ????????????throw?(MyException)e.fillInStackTrace();??
- ????????}??
- ????}??
- ????public?static?void?main(String[]?args)?{??
- ????????????try?{??
- ????????????????new?ExceptionDemo1().h();??
- ????????????}?catch?(MyException?e)?{??
- ????????????????//?TODO?Auto-generated?catch?block??
- ????????????????e.printStackTrace();??
- ????????????}??
- ????}??
- }??
- </span>??
运行结果:
f
g
h
main
mine.util.exception.MyException: 自定义异常
?at demo.others.ExceptionDemo1.f(ExceptionDemo1.java:7)
?at demo.others.ExceptionDemo1.g(ExceptionDemo1.java:11)
?at demo.others.ExceptionDemo1.h(ExceptionDemo1.java:16)
?at demo.others.ExceptionDemo1.main(ExceptionDemo1.java:35)
mine.util.exception.MyException: 自定义异常
?at demo.others.ExceptionDemo1.f(ExceptionDemo1.java:7)
?at demo.others.ExceptionDemo1.g(ExceptionDemo1.java:11)
?at demo.others.ExceptionDemo1.h(ExceptionDemo1.java:16)
?at demo.others.ExceptionDemo1.main(ExceptionDemo1.java:35)
mine.util.exception.MyException: 自定义异常
?at demo.others.ExceptionDemo1.f(ExceptionDemo1.java:7)
?at demo.others.ExceptionDemo1.g(ExceptionDemo1.java:11)
?at demo.others.ExceptionDemo1.h(ExceptionDemo1.java:16)
?at demo.others.ExceptionDemo1.main(ExceptionDemo1.java:35)
mine.util.exception.MyException: 自定义异常
?at demo.others.ExceptionDemo1.h(ExceptionDemo1.java:30)
?at demo.others.ExceptionDemo1.main(ExceptionDemo1.java:35)
分析上面的程序,首先main函数被调用,然后是调用h函数,再g函数、f函数,f函数抛出异常,并在h函数捕获,这时将依次从栈顶到栈底输出异常栈路径。
4、异常链
有时候我们会捕获一个异常后在抛出另一个异常,如下代码所示:
[java]?view plaincopy?- <span?style="font-size:16px;">package?demo.others;??
- ??
- import?java.io.IOException;??
- ??
- import?mine.util.exception.MyException;??
- ??
- public?class?ExceptionDemo2?{??
- ????public?void?f()?throws?MyException?{??
- ????????throw?new?MyException("自定义异常");??
- ????}??
- ??
- ????public?void?g()?throws?Exception??{??
- ????????try?{??
- ????????????f();??
- ????????}?catch?(MyException?e)?{??
- ????????????e.printStackTrace();??
- ????????????throw?new?Exception("重新抛出的异常1");??
- ????????}??
- ????}??
- ??
- ????public??void?h()?throws?IOException????{??
- ????????try?{??
- ????????????g();??
- ????????}?catch?(Exception?e)?{??
- ????????????//?TODO?Auto-generated?catch?block??
- ????????????e.printStackTrace();??
- ????????????throw?new?IOException("重新抛出异常2");??
- ????????}??
- ????}??
- ????public?static?void?main(String[]?args)?{??
- ????????????try?{??
- ????????????????new?ExceptionDemo2().h();??
- ????????????}?catch?(IOException?e)?{??
- ????????????????//?TODO?Auto-generated?catch?block??
- ????????????????e.printStackTrace();??
- ????????????}??
- ????}??
- }??
- </span>??
运行结果:
mine.util.exception.MyException: 自定义异常
?at demo.others.ExceptionDemo2.f(ExceptionDemo2.java:9)
?at demo.others.ExceptionDemo2.g(ExceptionDemo2.java:14)
?at demo.others.ExceptionDemo2.h(ExceptionDemo2.java:23)
?at demo.others.ExceptionDemo2.main(ExceptionDemo2.java:32)
java.lang.Exception: 重新抛出的异常1
?at demo.others.ExceptionDemo2.g(ExceptionDemo2.java:17)
?at demo.others.ExceptionDemo2.h(ExceptionDemo2.java:23)
?at demo.others.ExceptionDemo2.main(ExceptionDemo2.java:32)
java.io.IOException: 重新抛出异常2
?at demo.others.ExceptionDemo2.h(ExceptionDemo2.java:27)
?at demo.others.ExceptionDemo2.main(ExceptionDemo2.java:32)
从结果中我们可以看出,异常栈变小了。也就是说丢失了最原始的异常信息。怎样保存最原始的异常信息呢?Throwable类中有个Throwable? cause属性,表示原始异常。通过接收cause参数的构造器可以把原始异常传递给新异常,或者通过initCause()方法。如下示例:
[java]?view plaincopy?- <span?style="font-size:16px;">package?demo.others;??
- ??
- import?java.io.IOException;??
- ??
- import?mine.util.exception.MyException;??
- ??
- public?class?ExceptionDemo2?{??
- ????public?void?f()?throws?MyException?{??
- ????????throw?new?MyException("自定义异常");??
- ????}??
- ??
- ????public?void?g()?throws?Exception??{??
- ????????try?{??
- ????????????f();??
- ????????}?catch?(MyException?e)?{??
- ????????????e.printStackTrace();??
- ????????????throw?new?Exception("重新抛出的异常1",e);??
- ????????}??
- ????}??
- ??
- ????public??void?h()?throws?IOException????{??
- ????????try?{??
- ????????????g();??
- ????????}?catch?(Exception?e)?{??
- ????????????//?TODO?Auto-generated?catch?block??
- ????????????e.printStackTrace();??
- ????????????IOException?io=new?IOException("重新抛出异常2");??
- ????????????io.initCause(e);??
- ????????????throw?io;??
- ????????}??
- ????}??
- ????public?static?void?main(String[]?args)?{??
- ????????????try?{??
- ????????????????new?ExceptionDemo2().h();??
- ????????????}?catch?(IOException?e)?{??
- ????????????????//?TODO?Auto-generated?catch?block??
- ????????????????e.printStackTrace();??
- ????????????}??
- ????}??
- }??
- </span>??
?
结果:
mine.util.exception.MyException: 自定义异常
?at demo.others.ExceptionDemo2.f(ExceptionDemo2.java:9)
?at demo.others.ExceptionDemo2.g(ExceptionDemo2.java:14)
?at demo.others.ExceptionDemo2.h(ExceptionDemo2.java:23)
?at demo.others.ExceptionDemo2.main(ExceptionDemo2.java:34)
java.lang.Exception: 重新抛出的异常1
?at demo.others.ExceptionDemo2.g(ExceptionDemo2.java:17)
?at demo.others.ExceptionDemo2.h(ExceptionDemo2.java:23)
?at demo.others.ExceptionDemo2.main(ExceptionDemo2.java:34)
Caused by: mine.util.exception.MyException: 自定义异常
?at demo.others.ExceptionDemo2.f(ExceptionDemo2.java:9)
?at demo.others.ExceptionDemo2.g(ExceptionDemo2.java:14)
?... 2 more
java.io.IOException: 重新抛出异常2
?at demo.others.ExceptionDemo2.h(ExceptionDemo2.java:27)
?at demo.others.ExceptionDemo2.main(ExceptionDemo2.java:34)
Caused by: java.lang.Exception: 重新抛出的异常1
?at demo.others.ExceptionDemo2.g(ExceptionDemo2.java:17)
?at demo.others.ExceptionDemo2.h(ExceptionDemo2.java:23)
?... 1 more
Caused by: mine.util.exception.MyException: 自定义异常
?at demo.others.ExceptionDemo2.f(ExceptionDemo2.java:9)
?at demo.others.ExceptionDemo2.g(ExceptionDemo2.java:14)
?... 2 more
从结果中看出当获取到“重新抛出异常2的时候,同时可以输出原始异常“重新抛出的异常1“和原始异常”自定义异常,这就是异常链。
5、finally的使用
finally子句总是执行的,通常用来做一些清理工作,如关闭文件,关闭连接等
下面举几个finally的例子:
[java]?view plaincopy?- <span?style="font-size:16px;">//?读取指定路径文本文件??
- ????public?static?String?read(String?filePath)?{??
- ????????StringBuilder?str?=?new?StringBuilder();??
- ????????BufferedReader?in?=?null;??
- ????????try?{??
- ????????????in?=?new?BufferedReader(new?FileReader(filePath));??
- ????????????String?s;??
- ????????????try?{??
- ????????????????while?((s?=?in.readLine())?!=?null)??
- ????????????????????str.append(s?+?'\n');??
- ????????????}?finally?{??
- ????????????????in.close();??
- ????????????}??
- ????????}?catch?(IOException?e)?{??
- ????????????//?TODO?Auto-generated?catch?block??
- ????????????e.printStackTrace();??
- ????????}??
- ????????return?str.toString();??
- ????}</span>??
分析:如果调用in = new BufferedReader(new FileReader(filePath));时发生异常,这时是一个文件路径不存在的异常,也就是说并没有打开文件,这时将会直接跳到catch块,而不会执行try...finally块(并不是finally子句)里面的语句in.close();此时不需要关闭文件。
再看一个例子,会导致异常的丢失
[java]?view plaincopy?- <span?style="font-size:16px;">package?demo.others;??
- ??
- import?mine.util.exception.MyException;??
- ??
- public?class?ExceptionDemo3?{??
- ????public?void?f()?throws?MyException?{??
- ????????throw?new?MyException("异常1");??
- ????}??
- ??
- ????public?void?g()?throws?MyException?{??
- ????????throw?new?MyException("异常2");??
- ????}??
- ??
- ????public?static?void?main(String[]?args)?{??
- ??
- ????????try?{??
- ????????????ExceptionDemo3?ex?=?new?ExceptionDemo3();??
- ????????????try?{??
- ????????????????ex.f();??
- ????????????}?finally?{??
- ????????????????ex.g();//此时捕获g方法抛出的异常,f方法抛出的异常丢失了??
- ????????????}??
- ????????}?catch?(MyException?e)?{??
- ????????????System.out.println(e);??
- ????????}??
- ??
- ????}??
- }??
- </span>??
结果:mine.util.exception.MyException: 异常2
此时异常1就丢失了
或者这样写:
- <span?style="font-size:16px;">package?demo.others;??
- ??
- import?mine.util.exception.MyException;??
- ??
- public?class?ExceptionDemo3?{??
- ??
- ????public?void?g()?throws?MyException?{??
- ????????throw?new?MyException("异常2");??
- ????}??
- ??
- ????public?static?void?main(String[]?args)?{??
- ????????ExceptionDemo3?ex?=?new?ExceptionDemo3();??
- ????????try?{??
- ????????????ex.g();??
- ????????}?finally?{??
- ????????????//直接return会丢失所以抛出的异常??
- ????????????return;??
- ????????}??
- ??
- ????}??
- }??
- </span>??
6、异常的限制
(1)当覆盖方法时,只能抛出在基类方法的异常说明里列出的那些异常,有些基类的方法声明抛出异常其实并没有抛出异常,这是因为可能在其子类的覆盖方法中会抛出异常
(2)构造器可以抛出任何异常而不必理会基类构造器所抛出的异常,派生类构造器异常说明必须包含基类构造器异常说明,因为构造派生类对象时会调用基类构造器。此外,派生类构造器不能捕获基类构造器抛出的异常。