本文写作的主要原因是博主在项目中遇到异常时经常不知道如何处理,每次面对异常都block好久......特写此文来总结规范一下异常的处理方式。本文参考了网上好多文章,在这就不贴了。
目录
1.异常体系
异常是指由于各种不期而至的情况,导致程序中断运行的一种指令流,如:文件找不到、非法参数、网络超时等。为了保证正序正常运行,在设计程序时必须考虑到各种异常情况,并正确的对异常进行处理。记住,处理异常并不一定是把这种错误的异常改正,让程序走上正确的道路。异常真正的目的是让你知道会有这种情况,异常信息的记录才是最主要的。一些新手总是强迫症似的想要把发生异常的程序改正而导致无从下手,这种观念是不对的。异常也是一种对象,java当中定义了许多异常类,并且定义了基类java.lang.Throwable作为所有异常的超类。Java语言设计者将异常划分为两类:Error和Exception,其体系结构大致如下图所示:
Throwable:有两个重要的子类:Exception(异常)和Error(错误),两者都包含了大量的异常处理类。
1、Error(错误):是程序中无法处理的错误,表示运行应用程序中出现了严重的错误。此类错误一般表示代码运行时JVM出现问题。通常有Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如说当jvm耗完可用内存时,将出现OutOfMemoryError。此类错误发生时,JVM将终止线程。
这些错误是不可查的,非代码性错误。因此,当此类错误发生时,应用不应该去处理此类错误。
2、Exception(异常):程序本身可以捕获并且可以处理的异常。Exception这种异常又分为两类:运行时异常和编译异常。
1)运行时异常(不受检异常):RuntimeException类极其子类表示JVM在运行期间可能出现的错误。比如说试图使用空值对象的引用(NullPointerException)、数组下标越界(ArrayIndexOutBoundException)。此类异常属于不可查异常,一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。
2)编译异常(受检异常):Exception中除RuntimeException极其子类之外的异常。如果程序中出现此类异常,比如说IOException,必须对该异常进行处理,否则编译不通过。在程序中,通常不会自定义该类异常,而是直接使用系统提供的异常类。
以上是JVM层面上定义的三大类异常,站在用户处理的角度,异常可划分为两类:
1、可查异常:编译器要求必须处理的异常。正确的程序在运行过程中,经常容易出现的、符合预期的异常情况。一旦发生此类异常,就必须采用某种方式进行处理。除RuntimeException及其子类外,其他的Exception异常都属于可查异常。编译器会检查此类异常,也就是说当编译器检查到应用中的某处可能会此类异常时,将会提示你处理本异常——要么使用try-catch捕获,要么使用throws语句抛出,否则编译不通过。
2、不可查异常:编译器不会进行检查并且不要求必须处理的异常,也就说当程序中出现此类异常时,即使我们没有try-catch捕获它,也没有使用throws抛出该异常,编译也会正常通过。该类异常包括运行时异常(RuntimeException极其子类)和错误(Error)。
2.异常处理
2.1捕获异常
具体来说就是使用try-catch-finally进行捕获。具体的过程不说了,说一下我之前不知道的点。
1)finally不被执行的情况
在前边的代码中使用System.exit()退出应用。
程序所在的线程死亡或者cpu关闭。
如果在finally代码块中的操作又产生异常,则该finally代码块不能完全执行结束,同时该异常会覆盖前边抛出的异常。
2)finally子句含有return或异常
当try中含有return语句并且try后面有finally语句时,try会先执行完return语句但是并不真正返回,栈帧中保存有返回值的引用,等finally执行完再将该引用指向的值返回。也就是说如果finally中改变引用所指向的值,那么返回值也将改变。
finally子句中含有return语句且try中也含有return语句时,首先会执行到try中的return语句,然后执行finally语句直接return。
finally子句中执行的重新抛出了异常,此时会将前面try中catch到的异常覆盖。但这样是不好的,因为try中抛出的异常可能会更有意义。对于那种资源关闭例如close方法在finally中重新抛出异常,可以使用带资源的try语句。如果try语句块抛出一个异常,close方法也抛出一个异常,怎么办呢?原来的异常会重新抛出,而close方法抛出的异常会“被抑制”。这些异常将自动捕获,并由addSuppressed方法增加到原来的异常,可以调用getSuppressed方法得到从close方法抛出并被抑制的异常列表;对于更一般的异常,可以采用以下繁琐的方式处理(不建议,但是我也不知道更好的哈哈哈)
Exception ex = null;
try
{
try
{
code that might throw exceptions
}
catch(Exception e)
{
ex = e;
throw e;
}
}
finally
{
try
{
in.close();
}
catch(Exception e)
{
if(ex == null) throw e;
}
}
3)异常链
在实际开发过程中经常在捕获一个异常之后抛出另外一个异常,并且我们希望在新的异常对象中保存原始异常对象的信息,实际上就是异常传递,即把底层的异常对象传给上层,一级一级,逐层抛出。当程序捕获了一个底层的异常,而在catch处理异常的时候选择将该异常抛给上层…这样异常的原因就会逐层传递,形成一个由低到高的异常链。看如下代码:
try
{
access the database
}
catch (SQLException e)
{
ServletException se = new ServletException("database error");
se.initCause(e); //对SQLException进行包装,为了出问题的时候能追根究底
throw se;
}
等价方式:
try
{
access the database
}
catch (SQLException e)
{
ServletException se = new ServletException("database error",e);
throw se;
}
在以上的代码中,使用了initCause(e)方法将原异常放进去,这样如果新的ServletException被捕获了,直接使用getCause()方法可以获取原始异常。或者直接使用printStackTrace会打印出整个异常链。
同时,如果在一个方法中发生了一个受查异常,而不允许抛出它,那么包装技术就十分有用。我们可以捕获这个异常,并将它包装成一个运行时异常。
2.2直接抛出异常
对于每个方法可能会抛出的异常,决定是自己处理还是添加到throws列表中。对于后一种情况,也不必犹豫,将异常直接交给能胜任的处理器进行处理要比压制对它的处理更好。如果能处理,就尽量将异常控制在较小的范围。
还有一个需要注意的点是,如果在子类中将父类方法的异常抛出,只允许子类抛出的异常小于等于父类抛出异常的范围。
2.3带资源的try语句
jdk 1.7之后出现的一个新的语句,主要用于关闭资源。
在jdk 1.7之后出现了带资源的try语句,它允许在try关键字后紧跟一对圆括号,圆括号可以声明、初始化一个或多个资源(此处的资源是指那些必须在程序结束时显式关闭的资源,比如数据库连接,网络连接等),try-with-resources 是一个定义了一个或多个资源的try 声明,try语句在该语句结束时自动关闭这些资源。try-with-resources确保每一个资源在处理完成后都会被关闭。这些资源必须实现AutoCloseable或者Closeable接口,实现这两个接口就必须实现close() 方法。示例如下:
try(Scanner in = new Scanner(new FileInputStream(“d:\\haha.txt”));
PrintWriter out = new PrintWriter(“d:\\hehe.txt”)) {
while(in.hasNext()) {
out.println(in.next().toUpperCase());
}
}
一个try-with-resourcse声明了包含两个对象的声明,用分号隔开(某些书里错写成了逗号)。和声明一个对象相同,会在结束后自动调用close方法。 此外,try-with-resources 可以跟catch和finally,catch和finally中的对象是在try-with-resources里声明的对象关闭之后才执行(并不建议这样)。
如果try块和close块都抛出一个异常,那么close块的异常将会“被抑制”。这些异常将会自动捕获,并由addSuppressed方法增加到原来的异常。如果对这个异常感兴趣,可以调用getSuppressed方法,将会得到从close方法抛出并被抑制的异常列表。API如下:
1)void addSuppressed(Throwable t) 为这个异常增加一个“抑制”异常。这出现在带资源的try语句块中,其中t是close方法抛出的一个异常。
2)Throwable[ ] getSuppressed( ) 得到这个异常的所有“抑制”异常。一般来说,这些是带资源的try语句中close方法抛出的异常。