Java核心技术-异常处理

异常分类:
所有的异常都是由Throwable继承而来的,但在下一层立即分解为两个分支:
Error和Exception。
Error类层次结构描述了Java运行时系统内部错误和资源耗尽错误。应用程序不应该抛出这种类型的对象。如果出现了这样的内部错误,除了通告给用户,并尽力使程序安全地终止之外,再也无能为力了。这种情况很少出现。
Exception类层次结构又分解为两个分支:
一个分支派生于RuntimeException;另一个分支包含其他异常。
划分两个分支的规则是:由程序错误导致的异常属于RuntimeException;
而程序本身没有问题,但由于像I/O错误这类问题导致的异常属于其他异常。
派生于RuntimeException的异常包含下面几种情况:
1)错误的类型转换。
2)数组访问越界。
3)访问空指针
不是派生于RuntimeException的异常包括:
1)试图在文件尾部后面读取数据。
2)试图打开一个不存在的文件。
3)试图根据给定的字符串查找Class对象,而这个字符串表示的类并不存在。
“如果出现了RuntimeException”异常,那么就一定是你的问题。应该通过检测数组下标是否越界来避免ArrayIndexOutOfBoundsException异常;应该通过使用变量之前检测是否为空来杜绝NullPointerException异常的发生。
Java语言规范将派生于Error类或RuntimeException类的所有异常称为未检查异常(uncheck),所有其他的异常为已检查(checked)异常。

以下四种情况应该抛出异常:
1)调用一个抛出已检查异常的方法,例如:FileInputStream构造器
2)程序运行过程中发现错误,并且利用throw语句抛出一个已检查异常。
3)程序出现错误,例如a[-1] = 0 会抛出一个ArrayIndexOutOfBoundsException这样的未检查异常。
4)Java虚拟机和运行时库出现的内部错误。
如果出现前两种情况之一,则必须告诉调用这个方法的程序员有可能抛出异常。因为任何一个抛出异常的方法都有可能是个死亡陷阱。如果没有处理器捕获这个异常,当前执行的线程就会结束。

对于那些可能被他人使用的Java方法,应该根据异常规范,在方法的首部声明这个方法可能抛出的异常。
class Myanimation  throws IOException {
    public
    ... 

}
如果一个方法有可能抛出多个已检查异常,那么就必须在方法的首部列出所有的异常类,每个异常类之间用逗号隔开。
但是不需要声明Java的内部错误,即从Error和RuntimeException继承的错误和未检查异常,任何程序代码都具有抛出那些异常的潜能。
总之,一个方法必须声明所有可能抛出的已检查异常,而未检查异常要么不可控制(Error),要么就应该避免发生(RuntimeException)。如果方法没有声明所有可能发生的已检查异常,编译器就会给出一个错误消息。

捕获异常

    如果某个异常发生的时候没有在任何地方进行捕获,那程序就会终止执行,并在控制台上打印出异样信息,其中包括异常的类型和堆栈的内容。
    想要捕获一个异常,必须设置try/catch语句块,如果在try语句块中的任何代码跑出了一个在catch子句中说明的异常类,那么:
    1)程序将跳过try语句块的其余代码。
    2)程序将执行catch子句中的处理器代码。
    如果在try语句块中的代码没有抛出任何异常,那么程序将跳过catch子句。
    如果方法中的任何代码抛出了一个在catch子句中没有声明的异常类型,那么这个方法就会立刻退出(希望调用者为这种类型的异常设计了catch子句)。
    还有一种做法,将异常传递给调用者,让调用方法的人去操心。
    通常,应该捕获那些知道如何处理的异常,而将那些不知道怎么处理的异常继续进行传递。如果想传递一个异常,就必须在方法的首部添加一个throws说明符,以便告知这个方法可能会抛出异常。
    仔细阅读一下Java API 文档,以便知道每个方法可能会抛出哪种异常,然后再决定是自己处理,还是添加到throws列表中。对于后一种情况,也不必犹豫,将异常直接交给能够圣人的处理器进行黑醋栗要比压制对他的处理更好。
    特别地:如果编写一个覆盖超类的方法,而这个方法又没有抛出异常,那么这个方法就必须捕获方法代码中出现的每一个已检查异常。不允许在子类的throws说明符中出现超过超类方法所列出的异常类范围。

捕获多个异常:

在一个try语句块中可以捕获多个异常类型,对不同的异常做出不同的处理。
注意:捕获多个异常时,异常变量隐含为final变量。例如在以下子句体中为e赋不同的值:catch(FileNotFoundException | UnknownHostException e){...}//False

再次抛出异常与异常链:

在catch子句中可以抛出一个异常,这样做的目的是改变异常的类型。
eg:
try
{
    access the database
}
catch(SQLException e)
{   
    throw new ServletException("database error:"+e.getMessage());

}
这里ServletException用带有异常信息文本的构造器来构造,不过有一种更好的处理方法,并且将原始异常设置为新异常的“原因”;
 try
{
    access the database
}
catch(SQLException e)
{   
    throwable se = new ServletException("database error);
    se.initCause(e);
    throw se;
}
当捕获到异常时,就可以使用下面这条语句重新得到原始异常:
Throwable e = se.getCause();
强烈建议使用这种包装技术。这样可以让用户抛出子系统中的高级异常,而不会丢失原始异常的细节。
提示:如果在一个方法中发生了一个已检查异常,而不允许抛出它,那么包装技术就十分有用。我们可以捕获这个已检查异常,并将它包装成一个运行时异常。
有时候,我们可能只是想记录一个异常,再将他重新抛出,而不做任何改变,
 try
{
    access the database
}
catch(Exception e)
{
    logger.log(level,message,e);
    throw e;
}
在JavaSE 7 之前,这种方法存在一个问题,假设这个代码在以下方法中:
public void updateRecord() throws SQLException
Java编译器查看catch块中的throw语句,然后查看e的类型,会指出这个方法可以抛出任何Exception而不只是SQLException。现在这个问题已经有所改进,编译器会跟踪到e来自try块。假设这个try块中仅有的已检查异常是SQLException实例,另外,假设catch快中未改变,将外围方法声明为throws SQLExceoption就是合法的。

finally 子句

当代码抛出一个异常时,就会终止方法中剩余代码的处理,并退出这个方法的执行,如果方法获得了一些本地资源,并且只有这个方法自己知道,又如果这些资源在退出方法之前必须被回收,那么就会产生资源回收问题。一种解决方案是捕获并重新抛出所有的异常。但是,这样比较乏味,这是因为需要在两个地方清除所分配的资源,一个在正常的代码中,另一个在异常代码中。
Java有一种更好的解决方案,就是finally子句。
不管是否有异常被捕获,finally子句中的代码都被执行。
执行finally子句三种情况:
1)代码没有抛出异常。
try全部语句 --> finally子句 --> try语句块之后的第一条语句
2)抛出一个在catch子句中捕获的异常。
程序将执行try语句块中所有代码,直到发生异常为止,跳过余下代码,执行与该异常匹配的catch子句中的代码,最后执行finally子句中的代码。
如果catch子句没有抛出异常,程序将执行try语句块之后的第一条语句。
3)代码抛出了一个异常,但这个异常不是由catch子句捕获的。
程序将执行try语句块中的所有语句,直到有异常被抛出为止,此时,将跳过try语句块中剩余代码,然后执行finally子句中的语句,并将异常抛给这个方法的调用者。

try语句可以只有finally子句,而没有catch子句。
InputStream in = ... ;
try
{
...
}
finally
{
 in.close();
}
无论在try语句块中是否遇到异常,finally子句中的in.close()都会被执行,当然,如果真的遇到一个异常,这个异常将会被重新抛出,并且必须由另一个catch子句捕获。
提示:强烈建议单独使用try/catch和try/finally语句块,这样可以提高代码的清晰度。
try 
{
    try
    {
    ...
    }
    finally 
    {
    in.close();
    }
}
catch(IOException e)
{
    show error message
}
内层的try语句块只有一个职责,就是确保关闭输入流,外层的太容易语句块也只有个职责就是确保报告出现的错误,这种方式不仅清楚,而且还具有一个功能,就是将会报告Finally子句中出现的错误。
警告:当finally子句包含return语句,而在try中也有return子句,在方法返回前finally子句中的内容将被执行
public static int f (int n )
{
try{
    int r = n * n;
    }
finally{
    if (n == 2 ) return 0 ;
    }
}
如果f(2)本来的结果为4,现在却成了2。
有时候,finally子句也会带来麻烦,例如,清理资源的方法也有可能抛出异常。
对此,Java SE 7 中关闭资源的处理会容易很多。

带资源的try语句

对于以下代码模式:
open a resource 
try {
     work with the resource 
}
finally{
    close the resource 
}
假设资源属于一个实现了AutoCloseable接口的类,其中有一个方法:
void close() throws Exception
带资源的try语句(try-with-resource)
try(Resource res = ...)
{
    work with it
}
try 块退出时,会自动调用res.close()
eg:
try (Scanner in = new Scanner(new FileInputStream("/a/words")))
{
    while(in.hasNext())
       System.out.println(in.next());
}
这个块正常退出时,或者存在异常时,都会调用in.close()方法,就好像使用了finally块一样。
还可以指定多个资源:
try (
Scanner in = new Scanner(new FileInputStream("/a/words"));
PrintWriter out = new PrintWriter("out.txt"))
{
    while(in.hasNext())
       System.out.println(in.next().toUpperCase());
}
常规方式,会需要两个try/finally语句。

未完待续。。。

你可能感兴趣的:(java核心技术,java基础,java,exception)