Java的基本理念是“结构不佳的代码不能运行”
为什么要使用异常? 首先我们可以明确一点就是异常的处理机制可以确保我们程序的健壮性,提高系统可用率 。异常不是程序语法错误,异常,就是在正常语法的代码运行过程中出现如 一楼所说的情况,如果不进行异常处理,那程序直接结束了,之所以捕获异常,是让你可以有发生错误补救的机会。 |
异常定义:异常情形是指阻止当前方法或者作用域继续执行的问题。在这里一定要明确一点:异常代码某种程度的错误,尽管Java有异常处理机制,但是我们不能以“正常”的眼光来看待异常,异常处理机制的原因就是告诉你:这里可能会或者已经产生了错误,您的程序出现了不正常的情况,可能会导致程序失败!
那么什么时候才会出现异常呢?只有在你当前的环境下程序无法正常运行下去,也就是说程序已经无法来正确解决问题了,这时它所就会从当前环境中跳出,并抛出异常。抛出异常后,它首先会做几件事。首先,它会使用new创建一个异常对象,然后在产生异常的位置终止程序,并且从当前环境中弹出对异常对象的引用,这时。异常处理机制就会接管程序,并开始寻找一个恰当的地方来继续执行程序,这个恰当的地方就是异常处理程序,它的任务就是将程序从错误状态恢复,以使程序要么换一种方法执行,要么继续执行下去。
开发异常处理的初衷是为了方便程序员处理错误,异常处理的重要原则是:只有在你知道如何处理的情况下才捕获异常。重要目标是:把错误处理的代码通错误发生的地点相分离,使你能专注于要完成的事情,至于错误处理,在另一段代码中完成。
1、Java中抛出异常只有两种方式:throw显示抛出,自动抛出(运行时异常)。
- 显示抛出异常:Java中的异常,一般需要通过throw关键字进行显示抛出,但是它不能单独使用,需要与try...catch/finally或throws联合使用。(try也不能单独使用,必须与catch或finally联合)
class SimpleException extends Exception{}//自定义异常类 try { throw new SimpleException(); } catch (Exception e) { e.printStackTrace(); }
或者
public static void main(String[] args) throws SimpleException {throw new SimpleException(); }
这两种方法的不同点是:try(检测异常代码块)... catch(处理异常) 是程序(catch)处理过异常,程序正常运行,不退出。
而throws仅仅是异常说明,仅仅把方法可能会抛出的异常告知使用此方法的客户端程序员,它使得调用者能确切知道写什么样的代码可以捕获所有潜在的异常。Java提供相应的语法(并强制使用用这个语法),是你能以礼貌的方式告知客户端程序员某个方法可能会抛出的异常类型,然后客户端程序员就可以进行相应的处理。它仅仅是异常说明,说明可能会抛出的异常类型,并不抛出异常。希望调用者处理此异常。
方法里的代码产生异常却没有进行处理,编译器会发现这个问题并提醒你:要么处理(try catch)要么在异常说明中表明此方法将产生异常(throws)。
- 自动抛出异常:
但是运行时异常如{NullPointerException,ArrayIndexOutOfBoundsException}是不需要程序进行显示抛出,它是自动被Java虚拟机抛出。不必在异常说明中把它列出来。这种异常属于错误,将被自动捕获。
如: int[] a=new int[4];
System.out.println(a[4]);
虽然不用捕获异常,但还是可以在代码中抛出运行时异常。
如果程序中抛出了运行时异常,可以处理,可以不处理。
处理:
int[] a=new int[4]; try{ System.out.println(a[4]);//此语句抛出异常,不会执行此后的try语句 System.out.print("hello"); //即不会执行 }catch (Exception e) { //找到相应的catch语句执行 System.out.print("越界"); } System.out.print("hello");//处理异常后会执行,如果不处理异常,则不执行
如果处理则程序跳出try语句去执行相应的catch语句,并顺序执行之后的语句即hello会打印出来,如果不处理则直接跳出程序,不打印hello,并打印e.printStackTrace()方法如下:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 4
at jin.feng2.Exception01.main(Exception01.java:18)
2、finally关键字(任何情况下都会执行)不论是return;break;continue;
对于一些代码,可能会希望无论try块中的异常是否抛出,它们都能得到执行。着通常适用于内存与回收之外的情况。(内存回收由垃圾回收器自动回收)
当要把出内存之外的资源恢复到它们的初始状态时,就需要用到finally子句。如:已经打开的文件或网络连接,在屏幕上画图形,开关等等。
try { System.out.println("Point 1"); if(i==1) return; System.out.println("Point 1"); if(i==2) return; }catch (Exception e) { e.printStackTrace(); } finally{ System.out.println("00000"); } //main() f(1); f(2);
//output
Point 1
00000
Point 1
Point 1
00000
返回之前会执行finally,即先执行finally再执行return.
finally关键字的误用导致异常丢失:
第一种是在异常处理之前调用finally,并在finially里抛出另一种异常。前面的异常则会丢失!
{ try { throw new RuntimeException(); } finally { System.out.println("e"); return; }
//output
jin.feng2.SimpleException2
另一种异常丢失是异常还未处理,在finally中执行return
3、异常的限制
当覆盖方法的时候,只能抛出在基类方法的异常说明里列出的异常。不能基于异常说明来重载方法,一个出现在基类方法的异常说明中的异常,不一定会出现在派生类中
{ try { throw new RuntimeException(); } finally { System.out.println("e"); return; }
方法的异常说明里,这与继承的规则相反。在继承中,基类的方法必须出现在派生类里。
异常的限制:对于继承类,它如果所覆盖的方法有异常说明,则所列出的异常类,必须是基类该方法所列出的异常类的子集
class MyException1 extends Exception { } class A { public void proc(){ } } class B extends A { // 编译错误:因为A.proc没有异常说明,所以子类也不能有异常说明 // 解决的方法是为A.proc加上异常说明:throws MyException1 // 或者在throw new MyException1();加上try块并去掉异常说明 public void proc() throws MyException1 { throw new MyException1(); } } class MyException1 extends Exception { } class MyException2 extends Exception { } class A { public void proc() throws MyException1 { } } class B extends A { // 错误:因为A.proc只声明了MyException1异常 public void proc() throws MyException2 { } }
构造器是一个例外,继承类可以声明更多的异常类,但必须加上基类所声明的异常类:
class MyException1 extends Exception { } class MyException2 extends Exception { } class A { A() throws MyException1 { } public void proc() throws MyException1 { } } class B extends A { B() throws MyException1, MyException2 { } }
当一个类实现继承了一个抽象类和一个接口,这个抽象类和接口中有一个方法是一样的,但是异常说明不一样,这时要兼顾抽象类和接口的异常说明,这个方法的实现一定是这两个异常说明的交集。
//abstract class public abstract void event() throws BaseballException; //interface public void event()throws rain; //implement class public void event(){}
必须是交集,所以为空
4、栈轨迹
printStackTrace()方法所提供的信息可以通过getStackTrace()方法来直接访问,这个方法将返回一个由栈轨迹中的元素所构成的数组,元素0表示栈顶,即最后调用的方法。先调用,后弹出。
void printStackTrace();
//output
java.lang.RuntimeException
at jin.feng2.Exception01.main(Exception01.java:26)
public class Exception03 { static void f(){ try { throw new Exception(); } catch (Exception e) { for(StackTraceElement ste:e.getStackTrace()) System.out.println(ste.getMethodName()); } } static void g(){f();} static void h(){g();} public static void main(String args[]){ f(); System.out.println("____________"); g(); System.out.println("____________"); h(); } }
//output
f
main
____________
f
g
main
____________
f
g
h
main
重新抛出异常:即在catch中又抛出了同一种异常。如果只是把当前异常对象重新抛出,那么printStackTrace()方法显示的将是原来的异常抛出点的调用栈,而不是重新抛出点的信息。如果想要跟新这个信息,可以调用fillInStackTrace()方法。即
throw e.fillInStackTrace
如果重新抛出另一种异常,则之前的异常栈都无法保存,但是希望把原始异常的信息保存下来——异常链
则需要使用initCause()方法。
如e.initCause(new NullPointerException);
throw e;