美文网首页
java 异常

java 异常

作者: ands999 | 来源:发表于2019-05-02 17:03 被阅读0次

Java中异常处理是识别及响应错误的机制。有效地异常处理能使程序更加健壮。异常处理是非功能性需求。

异常的处理机制

Java 通过面向对象的方法进行异常处理。每个异常都是一个对象。

当程序违反了Java的语义规则时,会自动生成一个异常类对象。两种情况会生成异常类对象。一种是内置的语义检查。另一种是自定义的异常,用throw关键字引发异常。

生成的异常对象将被提交给运行时系统,此过程为抛出(throw)异常。

当运行时系统接收到异常对象时,会寻找能处理此异常的代码并交给其处理,此过程为捕获(catch)异常。

如果运行时系统找不到可以捕获异常的方法,则将终止系统,结束程序。

异常类的层次结构

Java定义了一个异常类的层次结构,以java.lang.Throwable开始,扩展出Error和Exception,Exception又扩展出RuntimeException。

RuntimeException表示的是代码所导致的问题,如数组访问越界,空指针异常,类转换异常等。而派生自Exception类的异常所表示的并不是代码本身的问题所导致的非正常状态,而是应用本身无法控制的情况。例如一个应用在尝试打开一个文件并写入的时候,该文件已经被另外一个应用打开从而无法写入。

Error是一系列很难通过程序解决的问题。这些问题基本上是无法恢复的,例如内存、堆栈空间不足等。一般情况下,不会对从Error类派生的各个异常进行处理。

java为系统异常和普通异常提供了不同的解决方案,编译器强制普通异常必须被try..catch处理或用throws声明继续抛给上层调用方法,所以普通异常也称为checked异常;而系统异常可以处理也可以不处理,所以,编译器不强制用try..catch处理或用throws声明,所以系统异常也称为unchecked异常。

关键字try,catch,throw,throws,finally

try是指定一块需要预防"异常"的程序。catch子句是指定捕捉的"异常"的类型。 throw语句是抛出"异常"。 throws是标明方法可能抛出的"异常"。finally是确保不管发生什么"异常"都被执行的语句。

每当遇到一个try语句,"异常"的框架就放到堆栈上面,直到所有的try语句都完成。如果下一级的try语句没有对某种"异常"进行处理,堆栈就会展开,直到遇到有处理这种"异常"的try语句。

在JDK7中提供了try-with-resources机制, 它规定你操作的类只要是实现了AutoCloseable接口就可以在try语句块退出的时候自动调用close 方法关闭流资源。

处理异常

三种处理异常的基本模式:转换(translate)、重试(retry)和恢复(recover)。
转换经常用于处理受检异常(checked exception),在方法中异常无法抛出,并且无法恢复时使用。 在这种情况下,将其转换为运行时异常(runtime exception)而后抛出是最合适的做法。接下来,运行时异常通常由框架处理。
在处理不可靠的服务时,重试非常有用,前提是重新尝试有意义。一个很好的例子就是网络中断重试。如果定义了这种策略,那么就能够恢复到正常状态。例如,如果通过网络发送数据失败,可以将数据写入本地存储。当然,这时就必须定义如何处理该文件。此外,上面提到的模式可以组合。

// 转换
try {
    throw new IOException("Made up");
} catch (IOException e) {
    throw new RuntimeException(e);
}
// 重试5次后放弃
boolean end = false;
int count = 0;
while (end == false) {
    try {
        // 发送信息
        if (true) {
            throw new MessagingException("Made up");
        }
        end = true;
    } catch (MessagingException e) {
        if (count >= 5) {
            // 尝试5次放弃。
            throw new RuntimeException("was not able to send message even after five tries", e);
        }
        ++count;
        try {
            Thread.sleep(30000);
        } catch (InterruptedException e1) {
            Thread.currentThread().interrupt();
            throw new RuntimeException(e1);
        }
    }
}
// 恢复:如果传输失败记录到文件
try {
    // 发送信息
    throw new MessagingException("Made up");
} catch (MessagingException e) {
    try {
        // 写文件
        throw new IOException("Made up");
    } catch (IOException e1) {
        // 如果写文件失败,不再进行恢复
        throw new RuntimeException(e1);
    }
}

异常处理的建议

1 为可恢复的错误使用受检异常,为编程错误使用非受检异常。

受检异常保证你会针对错误情况提供异常处理代码,这是一种从语言层面上强制编写健壮代码的方式,但引入异常处理代码会导致代码可读性变差。当然,如果有可替代方式或恢复策略的话,捕获异常并做处理看起来似乎也合情合理。

例如,在批量处理任务中,如果是处理子任务时出现问题,那么只记录子任务的异常,继续处理其他的子任务。对失败的任务进行重试或者记录到日志中。

2 避免过度使用受检异常

在方法重写或接口实现中,受检异常会污染其他方法。特别是在API方法中,要慎用。

3 将受检异常转为运行时异常

这是在诸如Spring之类的框架中用来减少使用受检异常的方式之一。这样可以将特定的异常限制在特定的模块中,比如把 SQLException 抛到 DAO 层,把有意义的运行时异常抛到客户端层。

4 在 finally 程序块中关闭或者释放资源

这是 Java 编程中广为人知的最佳实践和事实上的标准,尤其是在处理网络和 IO 操作的时候。从 Java7 开始,新增加了一项功能:自动资源管理,或者称之为ARM块。尽管如此,仍然要记住在 finally 块中关闭资源,这对于释放像 FileDescriptors 这类资源至关重要。

5 避免空的 catch 块

空的 catch 块不仅隐藏了错误和异常,同时可能导致对象处于不可用状态或者脏状态。

6 提早抛出(迅速失败)

在导致错误的原始地方就抛出异常。一般是入参错误,导致其他地方抛出异常,这时应该先检查入参,如果有问题即刻抛出异常。

7 延迟处理(throw和throws)

在有能力处理的地方,才能抛出异常。

8 日志要记录完整异常上下文

 在程序执行期间,用日志记录错误依然是最好的方法。这在 Java 异常处理中不仅仅是一个最佳实践,而且是一个最通用的实践。而且,日志需要记录详细入参。什么何时何处为何。

在API中,受检异常的使用

 首先,Checked Exception应当只在异常情况对于API以及API的使用者都无法避免的情况下被使用。例如在打开一个文件的时候,API以及API的使用者都没有办法保证该文件一定存在。反过来,在通过索引访问数据的时候,如果API的使用者对参数index传入的是-1,那么这就是一个代码上的错误,是完全可以避免的。因此对于index参数值不对的情况,我们应该使用Unchecked Exception。

 其次,Checked Exception不应该被广泛调用的API所抛出。

 再次,Checked Exception应该有明确的意义。

 对于API的用户而言,一旦遇到API抛出Checked Exception,那么就需要考虑使用一个Wrapped Exception来将该Checked Exception包装起来。

参考

http://www.cnblogs.com/liaoliao/p/5009156.html
http://www.importnew.com/20139.html
dzone.com/articles/good-exception-handling

相关文章

网友评论

      本文标题:java 异常

      本文链接:https://www.haomeiwen.com/subject/swnweqtx.html