简单谈谈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给我们提供的异常机制,它好似一个长者,时不时给我们指引道路,也让我们在编码的时候没有那么无聊:)