(一)异常处理机制详解

# 前言

本文主要是对Java异常处理机制的阐述,了解Java的异常机制的设计和分类,及Java异常有哪些坑,如何在自定义异常类时避免采坑。

# 异常机制分类

异常情况是指阻止当前方法或作用域继续继续执行的情况。在Java中异常也是对象,我们可以像创建其他对象一样,用new在堆上创建异常对象。
从上图可以看到Throwable是所有异常类型的根类,它有两个重要的子类:Exception和Error。

  • Error(错误)

Error表示编译时和系统错误(除特殊情况外我们无需关注),比如代码允许是JVM运行错误,或内存不足时OutOfMemoryError。

  • Exception(异常)

Exception是可以抛出/处理的异常。在Java类库、用户方法及运行时故障都可能抛出Exception类型异常,我们程序员需要关注的主要是Exception。它又分为运行时异常和非运行时异常。

  运行时异常:由RuntimeException和其子类异常组成。比如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)。这些异常通常是非受检异常,可以捕获处理或者不处理。一般有程序逻辑引起的。运行时异常的特点是Java编译器编译时不会检查它,就算有这种异常编译也能通过,究其原因,RuntimeException代表的是编程错误。

  非运行时异常:包括RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常。

# try-catch-finally捕获异常

在Java中使用try-catch或者try-catch-finally捕获异常。

## try块

对于有可能出现异常情况的代码块Code,我们可以把它放在try块里。

try {
//可能发生异常的代码块
}

## 异常处理程序

异常处理程序必须紧跟在try块后,以关键字catch表示。当异常被抛出时,异常处理机制将负责搜寻参数与异常类型相匹配的第一个程序,然后进入catch子程序执行。

try {
//可能发生异常的代码块
} catch (Type1 id1) {
//捕获并处理异常类型为Type1的异常
} catch (Type2 id2) {
//捕获并处理异常类型为Type1的异常
} finally {
//无论如何都会走到的代码
//有如下极端情况不会走到finally代码块,但一般不考虑
//比如CPU掉电、线程异常终止等
}

// etc...

有时也可以采用maltiple catch,具体参考以下代码段。

## throw和throws

我们在编程时,需要针对某种异常情况抛出异常给客户端,代码如下

if (s == null) {
throw new NullPointerException();
}

throws是一种“异常说明”方式,它属于方法声明的一部分,跟在形式参数列表之后。

void func() throws Exception1, Exception2 {}

这种异常说明的方式,可以强制函数使用者强制处理该异常情况。在定义抽象基类和接口时这种能力很重要,这样派生类就可以处理这些预先声明的异常。

从上面可以看出throw主要是用来中断程序执行并移交异常对象到运行时处理。
throws用于声明方法可抛出的异常,是异常说明的一种机制。

## 使用finally做清理工作

对于一些代码,希望无论try块是否有异常抛出,都能得到执行,比如打开的文件句柄或者网络连接,可以使用try-catch-finally,代码如下所示。

public class FinallyWorks {
    static int count = 0;
    public static void main(String[] args) {
        while (true) {
            try {
                // count为0时抛异常
                if (count ++ == 0) {
                    throw new IOException();
                }
                System.out.println("No exception");
            } catch (Exception e) {//该句可以捕获所有异常
                System.out.println("IOException");
            } finally {
                System.out.println("In finally clause");
                if (count == 2) break;
            }
        }
    }
}
/**
* Output
**/
IOException
In finally clause
No exception
In finally clause

## 新特性

### multiple exception

如果一个try块中有多个异常要被捕获,catch块中的代码会变丑陋的同时还要用多余的代码来记录异常。有鉴于此,Java 7的一个新特征是:一个catch子句中可以捕获多个异常。示例代码如下:

catch(IOException | SQLException | Exception ex){
  log.warn(ex);
  throw new MyException(ex.getMessage());
}

### try-with-resources

try-with-resources[1][2] 语句会确保在try语句结束时关闭所有资源。实现了java.lang.AutoCloseable或java.io.Closeable的对象都可以做为资源。使用try-with-resources进行资源的自动关闭,在try子句中能创建一个资源对象,当程序的执行完try-catch之后,运行环境自动关闭资源。示例代码如下:

/**
* code 1
**/
try (FileInputStream fis = new FileInputStream("example.java")) { // line 1
     System.out.println("fis created in try-with-resources");
     doSomething(); // line2
} catch (Exception e) {
     e.printStackTrace();
}

在Java7之前我们使用finally进行资源的关闭,如下所示

/**
* code 2
**/
FileInputStream fis = new FileInputStream("example.java");
try {
     System.out.println("fis created in try-with-resources");
     doSomething(); // line 3
} catch (Exception e) {
     e.printStackTrace();
} finally {
    if (fis != null) {
        fis.close();// line 4
    }
}

异常屏蔽
请参见参考文献[1][2]

# 正确的使用异常

## 不要在finally中使用return关键字。

finally块中return返回后方法结束执行,会覆盖try块中的return语句,换句话说就是屏蔽了try块中的return语句。

/**
 * @author liangk
 * @date 18/09/2018
 */
public class FinallyReturn {
    public static void main(String[] args) {
        String result = finallyReturnTest();
        System.out.println(result);

    } 

    public static String finallyReturnTest() {
        try {
            System.out.println("finallyReturnTest start");
            String result = "Hello EveryBody!";
            return result;
        } finally {
            return "The finally block will be printed in the end";
        }
    }
}
/**
 * Output
 */
finallyReturnTest start
The finally block will be printed in the end

## finally 块必须对资源对象、流对象进行关闭。

如果JKD7及以上版本,可以使用上文介绍的try-with-resources方式

## 避免直接抛出RuntimeException及其子类。

更不允许抛出Exception或Throwable(建议抛出具体的异常对象)

## 建议采用预检查方式规避RuntimeException异常,而不应该catch的方式处理。

public void readPreferences(String fileName) {
    InputStream in = new FileInputStream(fileName);
}

上面的程序如果fileName是null,就会抛出NullPointerException,由于没有第一时间暴露问题,堆栈信息费解,需要相对复杂的定位。如果我们采取下面的方式,就很容易解决问题

public void readPreferences(String fileName) {
    Objects.requireNonNull(fileName);
    InputStream in = new FileInputStream(fileName);
}

## 不允许直接吞没异常。

直接吞没异常,既不处理也不抛出,可能会导致难以诊断的异常情况,无法判断异常从哪里结束,什么原因产生的异常情况。

## try块只包含可能会出现异常的必要代码段。
- try-catch 代码段会产生额外的性能开销,或者换个角度说,它往往会影响JVM对代码进行优化,所以建议仅捕获必要的代码段,不能包住整段代码。
- Java每实例化一个Exception,都会对当时的栈进行快照,这是一个相对较重的操作。如果异常频繁发生,开销就无法忽略。

## 不允许使用异常实现流程控制和条件控制。

我们可以利用break\continue \if else 配合finally实现流程控制。但利用异常控制流程,比通常意义上的条件语句(if/else、switch)要低效。

## try块放到事务代码中,catch异常后,如果需要回滚事务,一定注意手动回滚事务。

# 参考文献

[1] [详解try-with-resource](http://www.oracle.com/technetwork/cn/articles/java/trywithresources-401775-zhs.html)
[2] [try-with-resource官方文档](https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html)

原文地址:https://www.cnblogs.com/potato-not-tomato/p/9668728.html

时间: 2024-10-11 21:08:42

(一)异常处理机制详解的相关文章

java异常处理机制详解

java异常处理机制详解 程序很难做到完美,不免有各种各样的异常.比如程序本身有bug,比如程序打印时打印机没有纸了,比如内存不足.为了解决这些异常,我们需要知道异常发生的原因.对于一些常见的异常,我们还可以提供一定的应对预案.C语言中的异常处理是简单的通过函数返回值来实现的,但返回值代表的含义往往是由惯例决定的.程序员需要查询大量的资料,才可能找到一个模糊的原因.面向对象语言,比如C++, Java, Python往往有更加复杂的异常处理机制.这里讨论Java中的异常处理机制. 异常处理 Ja

PHP中的错误处理、异常处理机制详解

在编写PHP程序时,错误处理是一个重要的部分.如果程序中缺少错误检测代码,那么看上去很不专业,也为安全风险敞开了大门 例: <?php $a = fopen('test.txt','r'); //这里并没有对文件进行判断就打开了,如果文件不存在就会报错 ?> 那么正确的写法应该如下: <?php if (file_exists('test.txt')) { $f = fopen('test.txt', 'r'); // 使用完后关闭 fclose($f); } ?> 一.PHP错误

Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)

1 背景 还记得前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>中关于透过源码继续进阶实例验证模块中存在的点击Button却触发了LinearLayout的事件疑惑吗?当时说了,在那一篇咱们只讨论View的触摸事件派发机制,这个疑惑留在了这一篇解释,也就是ViewGroup的事件派发机制. PS:阅读本篇前建议先查看前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>,这一篇承接上一篇. 关于View与ViewGroup的区别在前一篇的A

【Hibernate步步为营】--锁机制详解

上篇文章详细讨论了hql的各种查询方法,在讨论过程中写了代码示例,hql的查询方法类似于sql,查询的方法比较简单,有sql基础的开发人员在使用hql时就会变得相当的简单.Hibernate在操作数据库的同时也提供了对数据库操作的限制方法,这种方法被称为锁机制,Hibernate提供的锁分为两种一种是乐观锁,另外一种是悲观锁.通过使用锁能够控制数据库的并发性操作,限制用户对数据库的并发性的操作. 一.锁简介 锁能控制数据库的并发操作,通过使用锁来控制数据库的并发操作,Hibernate提供了两种

浏览器缓存机制详解

对于浏览器缓存,相信很多开发者对它真的是又爱又恨.一方面极大地提升了用户体验,而另一方面有时会因为读取了缓存而展示了"错误"的东西,而在开发过程中千方百计地想把缓存禁掉.那么浏览器缓存究竟是个什么样的神奇玩意呢? 什么是浏览器缓存: 简单来说,浏览器缓存就是把一个已经请求过的Web资源(如html页面,图片,js,数据等)拷贝一份副本储存在浏览器中.缓存会根据进来的请求保存输出内容的副本.当下一个请求来到的时候,如果是相同的URL,缓存会根据缓存机制决定是直接使用副本响应访问请求,还是

Android触摸屏事件派发机制详解与源码分析

请看下面三篇博客,思路还是蛮清晰的,不过还是没写自定义控件系列哥们的思路清晰: Android触摸屏事件派发机制详解与源码分析一(View篇) http://blog.csdn.net/yanbober/article/details/45887547 Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇) http://blog.csdn.net/yanbober/article/details/45912661 Android触摸屏事件派发机制详解与源码分析三(Activi

SpringMVC视图机制详解[附带源码分析]

目录 前言 重要接口和类介绍 源码分析 编码自定义的ViewResolver 总结 参考资料 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/springMVC-introduction.html 本文将分析SpringMVC的视图这部分内容,让读者了解SpringMVC视图的设计原理. 重要接口和类介绍 1. View接口 视图基础接口,它的各种实现类是无

Shiro的Filter机制详解---源码分析

Shiro的Filter机制详解 首先从spring-shiro.xml的filter配置说起,先回答两个问题: 1, 为什么相同url规则,后面定义的会覆盖前面定义的(执行的时候只执行最后一个). 2, 为什么两个url规则都可以匹配同一个url,只执行第一个呢. 下面分别从这两个问题入手,最终阅读源码得到解答. 问题一解答 相同url但定义在不同的行,后面覆盖前面 如 /usr/login.do=test3 /usr/login.do=test1,test2 不会执行test3的filter

Android ViewGroup触摸屏事件派发机制详解与源码分析

PS一句:最终还是选择CSDN来整理发表这几年的知识点,该文章平行迁移到CSDN.因为CSDN也支持MarkDown语法了,牛逼啊! [工匠若水 http://blog.csdn.net/yanbober] 该篇承接上一篇<Android View触摸屏事件派发机制详解与源码分析>,阅读本篇之前建议先阅读. 1 背景 还记得前一篇<Android View触摸屏事件派发机制详解与源码分析>中关于透过源码继续进阶实例验证模块中存在的点击Button却触发了LinearLayout的事