通过异常处理问题
异常机制使代码的阅读、编写和调试工作更加井井有条。
基本异常
异常情形 是指阻止当前方法或作用域继续执行的问题。普通问题 是指在当前环境下能够得到足够的信息,总能处理这个错误(比如说if else)。而异常情形 是指在当前环境 下无法获得必要的信息来解决问题。此时你能做的就是从当前环境跳出 ,并且把问题提交给以上一级环境 。这就是所谓的抛出异常 。当抛出异常后,有几件事情随之发生。首先Java会在堆上创建异常对象 。然后,当前的执行路径被终止,并且从当前环境中弹出对异常对象的引用。此时异常处理机制接管程序,并开始寻找一个恰当的地方来继续执行程序。这个恰当的地方就是异常处理程序 ,它的任务是将程序从错误状态中恢复,以使程序要么换一种方式运行,要么继续运行下去。
if (obj == null ) {
throw new NullPointerException("obj is null" );
}
创建自定义异常
public class Test {
public static void main(String args[]) {
Test t = new Test();
try {
t.f();
} catch (MyException e) {
e.printStackTrace();
}
}
public void f() throws MyException {
throw new MyException("hello world!" );
}
}
class MyException extends Exception {
public MyException() {}
public MyException(String s) {super (s);}
}
import java.util.logging.*;
public class Test {
private static Logger logger = Logger.getLogger("Test" );
public static void main (String args[]) {
Test t = new Test();
try {
t.f();
} catch (MyException e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
logger.severe(sw.toString());
}
}
public void f () throws MyException {
throw new MyException("hello world!" );
}
}
捕获所有异常
可以只写一个异常处理程序来捕获所有类型的异常。通过捕获异常类型的基类Exception,就可以做到这一点。但是如果要捕获多种异常时,需要将基类异常放置在最后面。
栈轨迹
public class Test {
public static void main (String args[]) {
Test t = new Test();
t.f();
System.out .println("--------------------" );
t.g();
System.out .println("--------------------" );
t.h();
}
public void f () {
try {
throw new Exception();
} catch (Exception e) {
for (StackTraceElement ste : e.getStackTrace()) {
System.out .println(ste.getMethodName());
}
}
}
public void g () {f();}
public void h () {g();}
}
------------------------------执行结果如下:
f
main
--------------------
f
g
main
--------------------
f
g
h
main
重新抛出异常
我们可以在catch块中重新抛出异常,如果不做任何处理的话,该异常依旧保留了之前的信息。但是如果我们使用了fillInStackTrace() 方法,那么异常信息就会被更新。
public class Test {
public static void main (String args[]) {
Test t = new Test();
try {
t.g();
} catch (Exception e) {
e.printStackTrace();
}
try {
t.h();
} catch (Exception e) {
e.printStackTrace();
}
}
public void f () throws Exception {
throw new Exception();
}
public void g () throws Exception {
try {
f();
} catch (Exception e) {
throw e;
}
}
public void h () throws Exception {
try {
f();
} catch (Exception e) {
throw (Exception) e.fillInStackTrace();
}
}
}
-------------------执行结果
java.lang.Exception
at test.Test.f(Test.java:18 )
at test.Test.g(Test.java:22 )
at test.Test.main(Test.java:7 )
java.lang.Exception
at test.Test.h(Test.java:31 )
at test.Test.main(Test.java:12 )
还有一部分是关于异常链 的用法,我是看不下去了,有兴趣的可以看看。
Java标准异常
在简单了解Java的异常机制后,我们是否会思考,为什么平常没有为一些代码设置抛出异常,同样也会抛出异常呢?(比如ClassCastException 、NullPointerException )
RuntimeException
运行时异常 是由Java虚拟机 自动抛出的异常。其实我们平常一直都享受着运行时异常 的好处。不然的话每使用一个引用都抛出或捕捉一个NullPointerException 恐怕会累死你。
public void f () {
throw new NullPointerException();
}
属于运行时异常 的类型有很多,由于会被Java虚拟机 自动抛出,所以也就不用在异常说明 中将他们列出来。这些异常都是从RuntimeException 类继承而来。它们也被称为不受检查异常 。这种异常属于错误,将被自动捕获,就不用亲自动手了。
public class Test {
public static void main (String args[]) {
Test t = new Test();
t.g();
System.out .println("正常结束" );
}
public void f () {
throw new RuntimeException("异常结束" );
}
public void g () {
f();
}
}
------------ 运行结果:
Exception in thread "main" java.lang.RuntimeException: 异常结束
at test.Test.f(Test.java:10 )
at test.Test.g(Test.java:13 )
at test.Test.main(Test.java:6 )
如果不捕获该类型异常,则该异常会一直向上抛出。对于这种异常类型,编译器不需要异常说明 ,其输出被报告给了System.err 。即如果RuntimeException 没有被捕获而直达main(),那么程序在退出前将调用异常的printStackTrace() 方法。 小结:RuntimeException 之所以不需要捕获,究其原因,其代表的是编程错误:
无法预料的错误。比如从你控制范围之外传递进来的null 引用。 作为程序员,应该在代码中进行检查的错误。(比如对于ArrayIndexOutOfBoundsException ,就得注意一下数组的大小了。这种异常应该在编辑代码时规避,而不是运行时捕获。 )在一个地方发生的异常,常常会在另一个地方导致错误。 另外合理利用异常信息进行代码调试也是一项很重要的工作技巧。
使用finally进行清理
对于一些代码,可能会希望无论try块中的异常是否抛出,它们都能得到执行。这通常适用于内存回收之外的情况。此时我们并需要finally 块。普通例子不再列举了,下面看一些特殊情况的例子:
public class Test {
public static void main (String args[]) {
Test t = new Test();
t.test1();
System.out .println(t.test2());
}
void test1() {
try {
System.out .println("no exception" );
return ;
} finally {
System.out .println("finally" );
}
}
String test2() {
String s = null ;
try {
s = "2" ;
return s;
} finally {
s = "1" ;
}
}
}
----------------猜一猜运行结果吧
如果在try之中return了,return s这个操作会先入栈,这时s是”2”。在执行完finally中的代码后,s虽然被改变成了”1”。但是不会影响之前的return操作。差不多就是这个意思,可能描述有点错误。所以不必担心finally的操作会影响try中的retrun值。不过在return之前是会先去执行finally中的代码的。不过你要是在finally中return了,那就另当别论了。
小结
因为平常用法比较简单,所以我省略了很多小节。以下是关于如何使用异常 的总结:
在恰当的级别处理问题。(在知道该如何处理的情况下才捕获异常。) 解决问题并且重新调用产生异常的方法。 进行少许修补,然后绕过异常发生的地方继续执行。 用别的数据进行计算,以代替方法预计返回的值。 把当前运行环境下能做的事情尽量做完,然后把相同 的异常重抛到更高层。 把当前运行环境下能做的事情尽量做完,然后把不同 的异常重抛到更高层。 终止程序。 进行简化。(如果你的异常模式使问题变得太复杂,那用起来会非常痛苦也很烦人。) 让类库和程序更安全。(这既是在为调试做短期投资,也是在为程序的健壮性做长期投资。)