为什么需要异常?
以前用C写数据结构的时候,总有这样一个烦恼:比如写栈的Pop函数,除了在函数体中完成出栈的操作,还要使用一个返回值,表示出栈操作是否成功进行。
但是呢,为了将出栈的值返回给调用者,就要用return语句。但是return又被函数状态值占用了,于是只能用指针了,这就必须给pop函数加一个指针参数,用起来很不方便。
java内置了异常机制,函数可以尽管执行,如果出现了什么意外的事,异常就会发生,我们的程序可以通过异常来处理这些意外。这将函数的功能与函数的执行状态分离了,编写的代码更清晰。
java异常的继承结构和分类
java中所有的异常都是Throwable的子类,下面又分2个分支:Error和Exception。
Error不用我们管,当Error发生时,你应该责怪java自身。java运行时,本身系统出了问题,这并不是你的代码的问题。所以我们根本不用管Error这个分支。 Error一般很少发生。
Exception又可以分为2类:RuntimeException和 非RuntimeException异常。
RuntimeException异常(非检查异常)发生时,一定你的的代码写的有问题,具体说是代码的逻辑有问题,而不是语法问题,语法问题在编译时就会提示你。
比如使用了空指针,数组下标越界,无限循环造成的StackOverflow等。当这些异常发生时,你应该尽量去修改你的代码,而不是想着去捕获他们。这些都是可以避免的。
非RuntimeException异常(检查异常,图中深蓝色标记的),与RuntimeException相反,他们都是程序员不可避免的,比如出现执行一个IO操作,电脑可能没磁盘容量了,或者用户移除了IO设备。数据下载时,网络断开了......
非RuntimeException异常是java异常处理中我们最关心的异常分支。
另外:RuntimeException异常和非RuntimeException异常一个明显的区别是,如果使用Eclipse,编译器会提醒你,并强制你处理
非RuntimeException异常,而对于RuntimeException异常,编译器不会提醒你处理。
UIManager.setLookAndFeel是Swing中设置UI感官的方法,它会抛出:ClassNotFoundException等非RuntimeException异常
StringIndexOutOfBoundsException 是一个RuntimeException,Eclipse不会提醒你.
异常发生后会怎么样
首先要明确,异常是在方法内产生的,因为方法用来操作数据,操作数据就可能发生异常。
我们还要理解的是函数调用栈:函数的调用时以栈的形式管理的,层级调用,依次入栈。
当在某一个代码块中发生异常后,JVM就会去这个代码块层次中寻找异常处理器(形如:try....catch...finally),如果找到了相应的处理器,则执行处理器的代码,然后跳出,执行处理器后面的代码。如果在当前方法中找不到合适的处理器,则此方法终止执行(return 语句也不会执行),JVM递归的到这个函数的调用函数去找
一旦找到异常处理程序,则在那一层处理掉这个异常,然后,在那一层往后执行(异常在哪里被处理掉,执行流就在那里往后执行)。
如果一直回溯到栈底都没有异常处理程序,则此线程终止。
下面来看一个小例子:
public static void foo1() throws Exception { foo2(); } private static void foo2() throws Exception { throw new Exception(); } public static void main(String []args) { try { foo1(); } catch (Exception e){ // TODO Auto-generated catch block e.printStackTrace(); } }
我们可以使用异常的printStackTrace()方法打印异常的堆栈信息。
在代码中处理异常
主要要会用try...catch....finally语句。
1、catch(XXXException e) ,在异常发生,catch匹配到XXXException时,运行时系统就会将生成的异常对象赋值给变量e.这样,通过对异常变量e分析,就可以获取异常的详细信息异常。
2、finally主要用在异常处理器的收尾工作。关闭一些资源。比如一个文读一个件,无论成功读取,还是读取时发生异常了,最后都要关闭文件流,这个时候就要用到finally。
3、catch语句的顺序应该从具体异常到广泛异常,从特殊异常到一般异常。
try{ file.read(); }catch(EOFException e){ //EOFException是IOException的子类 }catch (IOException e){ //IOException是Exception的子类 }catch(Exception e){ }
4、同一个处理块处理多个异常
try
{
... } catch(AException | BException e) { System.out.println("抛出AExcpetion或者BException 都会执行这个代码块") }
5、如果子类复写了父类中一个会抛异常的方法,那么,子类的这个方法应该抛出父类相同的异常,或者是子类异常,或者不抛异常,总之要在父类异常的可控范围内。可以想一想,如果不遵循这个规则的话,那么多态就无法实现了。
6、如果一个方法出现异常了,也没处理,它会中断执行,不会有返回值。
7、如果在正常离开try是因为return,break ,continuer语句,则会先去执行finally,再执行他们。
在方法中抛出异常
如果一个方法中会抛出异常,并且这个方法本身不去处理,让调用者去决定如何处理。那么,它就必须使用throws语句在函数头进行声明它可能抛出的所有异常类型。
声明的作用就是告知调用者:我可能会抛出一个异常,你需要做好准备。
下面是Swing 中UIManager类中的setLookAndFeel的函数头,它声明自己会抛出的异常。
public static void setLookAndFeel(String className)throws ClassNotFoundException, InstantiationException, IllegalAccessException, UnsupportedLookAndFeelException { //... }
主动抛出异常
使用throw 语句,后接一个异常对象。抛出一个异常对象。
下面是一个将参数字符串转为大写的自定义函数。
private String toUpper(String s) throws Exception { if(s==null) throw new Exception("Paramter is null"); else if(s.equals("")) throw new Exception("Empty String"); else return s.toUpperCase(); }
处理异常,但又抛出异常
有时候,一个方法需要对发生的异常进行过滤,或者对调用者隐藏某些异常,只抛出调用者能理解的异常,那么,它就要对异常进行捕获处理,然后适当抛出。
例子函数:打印一个字符串的第一个字符
private void printFirst(String s) { try { System.out.println(s.charAt(0)); }catch(NullPointerException e) { throw e; //捕获到异常,然后抛给调用者 }catch(StringIndexOutOfBoundsException e) { //do nothing。自己处理,不通告调用者。 } }
定义自己的异常
异常的常用方法
e.getMessage() 打印异常的详细信息
e.printStackTrace() 打印异常的跟踪栈
e.getLocalizedMessage() 异常的本地化描述
class MyException extends Exception { public MyException() { super(); } public MyException(String msg) { super(msg); } }