【JAVA核心技术卷一】Exception异常

来源:互联网 发布:spss数据统计分析实例 编辑:程序博客网 时间:2024/06/05 09:22

Exception异常

异常分类:

  • Throwable
    • Error
    • Exception
    • OtherException
    • Runtime Exception

异常对象都是派生于Throwable类的一个实例,但在下一层立即分解为两个分支:ErrorException

Error类层次结构描述了Java运行时系统的内部错误和资源耗尽错误。如果出现了这样的内部错误,除了通告给用户,并尽力使程序安全地终止之外,再也无能为力。

Exception层次结构又分解为两个分支:一个派生于RuntimeException;另一个分支包含其他Exception。两个分支划分的规则是:由程序错误导致的异常属于RuntimeException;而程序本身没有问题,但由于像I/O这种错误问题导致的属于其他的异常。

派生于RuntimeException的异常包含以下几种情况:

  • 错误的类型转换
  • 数组访问越界
  • 访问空指针

不是派生于RuntimeException的异常包括:

  • 试图在文件尾部后面读取数据
  • 试图打开一个不存在的文件
  • 试图根据给定的字符串查找Class对象,而这个字符串表示的类不存在
  • ……

“如果出现RuntimeException异常,那么就一定是你的问题”

Java语言规范将派生于Error类或RuntimeException类的所有异常称为未检查(unchecked)异常,所有其他的异常称为已检查(checked)异常。编译器将核查是否为所有已检查的异常提供了异常处理器。

声明已检查异常

如果遇到了无法处理的情况,可以抛出一个异常。这个道理很简单:一个方法不仅需要告诉编译器将要返回什么值,还要告诉编辑器可能会发生什么错误

例如:

public FileInputStream(String name) throws FileNotFoundException

这个声明表示这个构造器将根据给定的String参数产生一个FileInputStream对象,但也有可能抛出一个FileNotFoundException异常。如果发生了这种情况,构造器将不会初始化一个新的FileInputStream对象,而是抛出一个FileNotFoundException类对象。如果这个方法真的抛出了一个异常对象,运行时系统就会开始搜索异常处理器,一边知道如何处理FileNotFoundException对象。

在自己编写方法时,不必将所有可能抛出的异常都声明。需要记住在遇到下面4中情况时应该抛出异常:

  1. 调用一个排除已检查异常的方法,例如FileInputStream构造器
  2. 程序运行过程中发现错误,并利用throw语句抛出一个已检查异常
  3. 程序出现错误。例如ArrayIndexOutOfBoundsException这样的未检查异常
  4. Java虚拟机和运行时库出现的内部错误

如果出现前两种情况之一,则必须告诉调用这个方法的程序员有可能抛出异常。

对于那些可能被他人使用的Java方法,应该在方法的首部声明这个方法可能抛出的异常

public Image loadImage(String s) throws IOException{}

如果一个方法可能抛出多个已检查异常,那么久必须在方法的首部列出所有的异常类,每个异常类用逗号隔开

public Image loadImage(String s) throws FileNotFoundException , EOFException{}

但是,不需要声明Java的内部错误,即从Error继承的错误。同样,也不应该声明从RuntimeException继承的那些未检查异常

void drawImage(int i) throws ArrayIndexOutOfBoundsException //  bad style :(

总之,一个方法必须声明所有可能抛出的已检查异常,而未检查异常要么不可控制(Error),要么就应该避免发生(RuntimeException)。如果方法没有声明或捕获所有可能发生的已检查异常,编译器就会给出一个错误信息。

如果在子类中覆盖了超类的一个方法,子类方法中声明的已检查异常不能比超类中更通用。如果超类方法中没有抛出任何已检查异常,子类也不能抛出任何已检查异常

如果类中的一个方法声明将会抛出一个异常,而这个异常是某特定类的实例时,则这个方法就由可能抛出一个这个类的异常,或者给这个类的任意一个子类的异常。

如何抛出异常

  1. 找到一个合适的异常类
  2. 创建这个类的一个对象
  3. 将对象抛出
String readData(Scanner in) throws EOFException{    ...    if(!in.hasNext()){  //遇到了End of File        if(n<len)            throw new EOFException(); //注意这里是throw没有s    }    ...}

一旦方法抛出了异常,这个方法就不可能返回到调用者

也可以自己创建一个异常类

class FileFormatException extends IOException{    ...}

注意!如果调用了一个抛出已检查异常的方法,就必须对它进行处理,或者将它继续传递!

捕获异常

想捕获一个异常,必须设置try/catch语句

try{  ...}catch(ExceptionType e){  handler}

如果在try语句块中的任何代码抛出了一个在catch子句中说明的异常类,那么程序将跳过try语句块的其余代码,并将执行catch子句中的处理器代码。

如果在try语句没有抛出任何异常,那么程序将跳过catch子句

如果方法中的任何代码抛出了一个在catch中没有声明的异常类型,那么这个方法会立刻退出

也可以捕获多个异常:

try{  ...}catch(ExceptionA a){  ...}catch(ExceptionB b){  ...}catch(ExceptionC c){  ...}

捕获多个异常时,异常变量隐含为final变量,因此,不能在子句体中为e赋不同 值

catch(ExceptionA|ExceptionB e){...} //error

再次抛出异常与异常链

catch子句中可以抛出一个异常,这样做的目的是改变异常的类型。

try{}catch(SQLException e){  throw new ServletException("database error:"+e.getMessage());}

这里,ServleException用带有异常信息文本的构造器来构造。不过,可以有一种更好的处理方法,并且将原是一场设置为新异常的“原因”:

try{}catch(SQLException){    Throwable se = new ServletException("database error");    se.initCause(e);    throw se;}

当捕获到异常时,就可以使用下面这条语句重写得到原始异常

Throwable e = se.getCause();

强烈建议使用这种包装技术,这样可以让用户抛出子系统中的高级异常,而不会丢失原始异常的细节。

如果在一个方法中发生了一个已检查异常,而不允许抛出它,那么包装技术就十分有用。我们可以捕获这个已检查异常,并将它包装成一个运行时异常

finally子句

当代码抛出一个异常时,就会终止方法中剩余代码的处理,并推出这个方法的执行。如果想继续执行程序,例如回收获得的本地资源,就要使用finally子句。

try语句中可以只有finally子句而没有catch子句

建议独立使用try/catchtry/finally语句块,这样可以提高代码的清晰度

InputStream in = ...;try{      try{          ...  }finally{          in.close()    }}catch(Exception e){  ...}

内层的try语句块只有一个职责,就是确保关闭输入流。外层的try语句块也只有一个职责,就是确保报告出现的错误。这种设计方式不仅清楚,而且还具有一个功能,就是将会报告finally子句中出现的错误

finally子句中包含return语句时,将会先执行finally中的return语句。

当然,finally子句也会带来麻烦。例如IO处理时

InputStream in = ...;try{    //code might throw exceptions}finally{    in.close();}

try语句块中的代码抛出了一些非IOException的异常,这些异常只有这个方法的调用者才能够给予处理。执行finally语句块并调用close方法。而close方法本身也有可能抛出IOException异常。当出现这种情况时,原始的异常将会丢失,转而抛出close方法的异常。如果你想做适当的处理,重新抛出原来的异常,代码会变得极其繁琐。

InputStream in = ...;Exception ex = null;try{    try{        //code might throw exceptions    }catch(Exception e){        ex = e;        throw e;    }}finally{    try{        in.close();    }catch(Exception e ){        in(ex ==null) throw e;    }}

带资源的try语句

不过在JAVA SE 7中,假设资源属于一个实现了AutoCloseable接口的类,这个接口有一个方法

void close() throws Exception 

另外还有一个Closeable接口。这是Autoacloseable的子接口,也包含一个close方法,不过这个方法声明为抛出一个IOExcepiton

带资源的try语句的最简形式为

try(Resource res = ){    //work with res}

try语句正常退出时,或者存在一个异常时,都会自动调用res.close(),就好像使用了finally块一样。

只要需要关闭资源,就要尽可能使用带资源的try语句

但是,使用异常的基本规则是:只在异常情况下使用异常机制

例如我们检测退栈操作前,要判断栈是否为空

if(!s.empty()){    s.pop();}

如果强行进行退栈操作并捕获异常

try{    s.pop()}catch(EmptyStackException){}

在测试中,调用isEmpty的运行时间为646毫秒,而捕获EmptyStackException的运行时间则为21739毫秒

2.不要过分地细化异常

3.利用异常层次结构

4.不要压制异常,即使这个方法有可能100年才抛出一个异常

5.在检查错误时,“苛刻”要比放任更好

6.不要羞耻于传递异常

0 0
原创粉丝点击