简单谈谈java的异常处理(Try Catch Finally)
异常的英文单词是exception,字面翻译就是“意外、例外”的意思,也就是非正常情况。事实上,异常本质上是程序上的错误,包括程序逻辑错误和系统错误。
一前言
java异常处理大家都不陌生,总的来说有下面两点:
1.抛出异常:throwexception
classSimpleException{ publicvoida()throwsException{ thrownewException(); }; }
2.捕获异常:
publicclassMyException{ publicstaticvoidmain(String[]args){ MyExceptione=newMyException(); SimpleExceptionse=newSimpleException(); try{ se.a(); }catch(Exceptione1){ e1.printStackTrace(); } } } classSimpleException{ publicvoida()throwsException{ thrownewException(); }; }
本文将在此基础上,更加深入的谈一些细节问题。
二自定义异常类
java语言为我们提供了很多异常类,但是有时候我们为了写代码的方便还是要自定义的去创造异常类:
classSimpleExceptionextendsException{};
创建好之后我们可以使用trycatch捕获它:
publicclassMyException{ publicstaticvoidmain(String[]args){ MyExceptione=newMyException(); try{ e.a(); }catch(SimpleExceptione1){ e1.printStackTrace(); } } publicvoida()throwsSimpleException{ thrownewSimpleException(); } } classSimpleExceptionextendsException{};
我们在MyException中定义了一个方法a(),让它抛出SimpleException异常,然后我们在main()中调用这个方法,并使用trycatch捕获了这个异常:
SimpleException atMyException.a(MyException.java:15) atMyException.main(MyException.java:8) atsun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethod) atsun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) atsun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) atjava.lang.reflect.Method.invoke(Method.java:606) atcom.intellij.rt.execution.application.AppMain.main(AppMain.java:144) Processfinishedwithexitcode0
编译执行后的结果,主要看前三行就行了。这里着重说明几点:
1.抛出异常类型的指定:(exceptionspecification)
当我们需要在一个方法中抛出一个异常时,我们使用throw后加某异常类的实例,程序会在此向客户端程序(调用这段代码的程序)抛出对应异常并在此退出(相当于return)。另外需要注意的是,我们必须在定义该方法的时候指明异常类型,比如下面这段代码会抛出SimpleException异常
publicvoida()throwsSimpleException
2.抛出多个异常:
publicvoida()throwsSimpleException,AException,BException{ thrownewSimpleException(); }
不同的异常类之间用逗号隔开即可,在这种情况下我们不必须throw每个异常类的实例(),但是客户端代码必须要catch到每个异常类:
publicclassMyException{ publicstaticvoidmain(String[]args){ MyExceptione=newMyException(); try{ e.a(); }catch(SimpleExceptione1){ e1.printStackTrace(); }catch(BExceptione1){ e1.printStackTrace(); }catch(AExceptione1){ e1.printStackTrace(); } } publicvoida()throwsSimpleException,AException,BException{ thrownewSimpleException(); } } classSimpleExceptionextendsException{}; classAExceptionextendsException{} classBExceptionextendsException{}
三stacktrace
无论是抛出异常,或者是捕获处理异常,我们的目的是为了写出更健壮的程序,这很大程度上依赖于java异常机制给我们提供的异常信息,而它的载体就是stacktrace。
前面的代码中我们直接使用printStackTrace()打印出异常信息,其实我们还可以使用getStackTrace()方法来获取StackTraceElement型的集合,如果你手头有IDEA的话,你可以先搜索出StackTraceElement类,可以发现它实现了接口Serializable,再看看它的类描述:
/** *Anelementinastacktrace,asreturnedby{@link *Throwable#getStackTrace()}.Eachelementrepresentsasinglestackframe. *Allstackframesexceptfortheoneatthetopofthestackrepresent *amethodinvocation.Theframeatthetopofthestackrepresentsthe *executionpointatwhichthestacktracewasgenerated.Typically, *thisisthepointatwhichthethrowablecorrespondingtothestacktrace *wascreated. * *@since1.4 *@authorJoshBloch */
讲的很清楚,这个类的每个实例都是stacktrace的一个元素,代表着一个stackframe,stacktrace是由getStackTrace()方法返回的。后边的我试着翻译了几遍,都觉得不好,还是直接上代码才能说清楚:
publicclassMyException{ publicstaticvoidmain(String[]args){ MyExceptione=newMyException(); e.a(); publicvoida(){ try{ thrownewException(); }catch(Exceptione){ StackTraceElement[]ste=e.getStackTrace(); System.out.println(ste.length); } } }
我们定义了方法a,让它抛出Exception异常的同时捕获它,然后我们通过getStackTrace()方法得到一个StackTraceElement型的数组,并打印出数组的长度:
7
Processfinishedwithexitcode0
我们把代码稍微改一下,不在a中捕获异常了,我们重新定义一个方法b,让它在调用a的同时将异常捕获:
publicclassMyException{ publicstaticvoidmain(String[]args){ MyExceptione=newMyException(); e.b(); } publicvoidb(){ try{ a(); }catch(Exceptione){ StackTraceElement[]ste=e.getStackTrace(); System.out.println(ste.length); } } publicvoida()throwsException{ thrownewException(); } }
结果如下:
8
Processfinishedwithexitcode0
别急,我们再来看点有趣的:
publicclassMyException{ publicstaticvoidmain(String[]args){ MyExceptionexception=newMyException(); try{ exception.c(); }catch(Exceptione){ StackTraceElement[]ste=e.getStackTrace(); System.out.println(ste.length); System.out.println("---------------------------------------------------------------"); for(StackTraceElements:e.getStackTrace()){ System.out.println(s.getClassName()+":method"+s.getMethodName()+"atline"+s.getLineNumber()); } System.out.println("---------------------------------------------------------------"); } } publicvoidc()throwsException{ try{ a(); }catch(Exceptione){ throwe; } } publicvoida()throwsException{ thrownewException(); } }
下面是结果:
8 --------------------------------------------------------------- MyException:methodaatline43 MyException:methodcatline39 MyException:methodmainatline9 sun.reflect.NativeMethodAccessorImpl:methodinvoke0atline-2 sun.reflect.NativeMethodAccessorImpl:methodinvokeatline57 sun.reflect.DelegatingMethodAccessorImpl:methodinvokeatline43 java.lang.reflect.Method:methodinvokeatline606 com.intellij.rt.execution.application.AppMain:methodmainatline144 --------------------------------------------------------------- Processfinishedwithexitcode0
也就是说,getStackTrace()返回一个栈,它包含从调用者(main())到初始抛出异常者(a())的一些基本信息,在上面的代码中,我们在c方法中调用a方法时捕获异常并通过throws将其再次抛出(rethrow),调用c方法的方法可以捕获并处理异常,也可以选择继续抛出让更高层次的调用者(靠近栈底)处理。rethrow虽然很方便,但存在着一些问题,我们看下面这段代码:
publicclassMyException{ publicstaticvoidmain(String[]args){ MyExceptionexception=newMyException(); try{ exception.c(); }catch(Exceptione){ e.printStackTrace(System.out); } } publicvoidc()throwsException{ try{ a(); }catch(Exceptione){ throwe; } } publicvoida()throwsException{ thrownewException("Exceptionfroma()"); } } java.lang.Exception:Exceptionfroma() atMyException.a(MyException.java:40) atMyException.c(MyException.java:30) atMyException.main(MyException.java:21)
我们在c中重新抛出e,在main中使用e.printStackTrace()打印出来,可以看到打印出来stacktrace还是属于a的,如果我们想把stacktrace变成c的可以这么写:
publicclassMyException{ publicstaticvoidmain(String[]args){ MyExceptionexception=newMyException(); try{ exception.c(); }catch(Exceptione){ e.printStackTrace(System.out); } } publicvoidc()throwsException{ try{ a(); }catch(Exceptione){ //throwe; throw(Exception)e.fillInStackTrace(); } } publicvoida()throwsException{ thrownewException("Exceptionfroma()"); } } java.lang.Exception:Exceptionfroma() atMyException.c(MyException.java:22) atMyException.main(MyException.java:10)
四异常链Exceptionchaining
先来看一个场景:
publicclassTestException{ publicstaticvoidmain(String[]args){ TestExceptiontestException=newTestException(); try{ testException.c(); }catch(CExceptione){ e.printStackTrace(); } } publicvoida()throwsAException{ AExceptionaException=newAException("thisisaexception"); throwaException; } publicvoidb()throwsBException{ try{ a(); }catch(AExceptione){ thrownewBException("thisisbexception"); } } publicvoidc()throwsCException{ try{ b(); }catch(BExceptione){ thrownewCException("thisiscexception"); } } } classAExceptionextendsException{ publicAException(Stringmsg){ super(msg); } } classBExceptionextendsException{ publicBException(Stringmsg){ super(msg); } } classCExceptionextendsException{ publicCException(Stringmsg){ super(msg); } }
创建了三个异常类AException、BException、CException,然后在a()中抛出AException,在b()中捕获AException并抛出BException,最后在c()中捕获BException并抛出CException,结果打印如下:
CException:thisiscexception atTestException.c(TestException.java:31) atTestException.main(TestException.java:8)
好,我们只看到了CException的信息,AException,BException的异常信息已丢失,这时候异常链的作用就出来了,看代码:
publicclassTestException{ publicstaticvoidmain(String[]args){ TestExceptiontestException=newTestException(); try{ testException.c(); }catch(CExceptione){ e.printStackTrace(); } } publicvoida()throwsAException{ AExceptionaException=newAException("thisisaexception"); throwaException; } publicvoidb()throwsBException{ try{ a(); }catch(AExceptione){ //thrownewBException("thisisbexception"); BExceptionbException=newBException("thisisbexception"); bException.initCause(e); throwbException; } } publicvoidc()throwsCException{ try{ b(); }catch(BExceptione){ //thrownewCException("thisiscexception"); CExceptioncException=newCException("thisiscexception"); cException.initCause(e); throwcException; } } } classAExceptionextendsException{ publicAException(Stringmsg){ super(msg); } } classBExceptionextendsException{ publicBException(Stringmsg){ super(msg); } } classCExceptionextendsException{ publicCException(Stringmsg){ super(msg); } }
我们用initCause()方法将异常信息给串联了起来,结果如下:
CException:thisiscexception atTestException.c(TestException.java:35) atTestException.main(TestException.java:8) atsun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethod) atsun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) atsun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) atjava.lang.reflect.Method.invoke(Method.java:606) atcom.intellij.rt.execution.application.AppMain.main(AppMain.java:144) Causedby:BException:thisisbexception atTestException.b(TestException.java:24) atTestException.c(TestException.java:32) ...6more Causedby:AException:thisisaexception atTestException.a(TestException.java:15) atTestException.b(TestException.java:21) ...7more Processfinishedwithexitcode0
五后记
其实关于java异常处理还有很多需要探讨的地方,但是由于我经验有限,还不能体会的太深刻,最常用的也就是
try{ ... }catch(Exceptione){ ... }finally{ //不管异常会不会被捕捉或者处理都会执行的代码,如关闭IO操作 }
但是无论如何我们还是要感谢java给我们提供的异常机制,它好似一个长者,时不时给我们指引道路,也让我们在编码的时候没有那么无聊:)