Java核心技术梳理-异常处理
一、引言
异常总是不可避免的,就算我们自身的代码足够优秀,但却不能保证用户都按照我们想法进行输入,就算用户按照我们的想法进行输入,我们也不能保证操作系统稳定,另外还有网络环境等,不可控因素太多,异常也不可避免。
但我们可以通过异常处理机制让程序有更好的容错性和兼容性,当程序出现异常时,系统自动生成Exception对象通知系统,从而将业务功能实现代码和错误处理代码分离。
异常处理已经成为衡量一门语言是否成熟的标志之一,增加了异常处理机制后程序有更好的健壮性和容错性。
二、异常处理机制
我们希望系统有一种机制能够将错误处理代码和正常实现代码分离开,相当于一个if else,if中为正常实现代码,而else为错误的处理。
2.1 try...catch 捕获异常
我们按照上面的思路,java利用try catch来进行异常捕获。
try{
    //业务代码
}
catch(IOException ex){
    //错误处理
}
catch(Exception ex){
    //错误处理代码
}
当try块代码出错时,系统生成一个异常对象,并将对象抛给运行环境,这个过程叫做抛出异常,运行环境接收到异常对象是,会寻找处理该异常对象的catch代码块,找到合适的catch块,就将对象给其处理,如果找不到,则运行环境终止,程序也将退出。
2.2 异常类继承体系
Java提供了丰富的异常类,这些异常类有严格的继承关系

从这个图可以看出异常主要分为两类,Error与Exception,Error错误一般是指与虚拟机相关的问题,如系统崩溃、虚拟机错误,这些错误无法恢复或不可能捕获,将导致应用程序崩溃,这些不需要我们去捕获。
在捕获异常时我们通常把Exception类放在最后,因为按照异常捕获的机制,从上至下判断该异常对象是否是catch中的异常类或其异常子类,一旦比较成功则用此catch进行处理。如果将Exception类放在前面,那么就会进行直接进入其中,因为Exception类是所有异常类的父类,那排在它后面的异常类将永远得不到执行的机会,这种机制我们称为先小后大。
2.3 多异常捕获
Java 7 开始,一个catch块中可以捕获多种类型的异常:
public static void main(String[] args) {
    try {
        Integer a = Integer.parseInt(args[0]);
        Integer b = Integer.parseInt(args[1]);
        Integer c = a / b;
        System.out.println(c);
    } catch (IndexOutOfBoundsException | NumberFormatException
            | ArithmeticException ie) {
        //异常变量默认final,不能重新赋值
        ie = new ArithmeticException("text");
    } catch (Exception ex) {
    }
}
多个异常之间用竖线(|)隔开,并且异常变量默认final,不能重新赋值。
2.4 获取异常信息
异常捕获后我们想要查看异常信息,可以通过catch后的异常形参来获得,常用的方法如下:
- getMessage():返回异常的详细描述字符串。 
- getStackTrace():返回异常跟踪栈信息。 
- printStackTrace():将异常跟踪栈信息按照标准格式输出。 
- printStackTrace(PrintStream p):将异常跟踪栈信息输出到指定输出流。 
2.5 finally回收资源
在try里面打开的一些物理连接,需要显示的回收,但显然在try里面或者catch里面进行回收是不行的,try中一旦出现异常,回收可能不会执行到,而catch更不行,因为在没有异常时根本不会执行,于是异常处理机制提供了finally,finally块代码一定会被执行,即是try中执行了return语句也还是会执行。
try{
    //业务代码
}catch(XXXException xx){
    //异常处理
}catch(XXXException xx){
}finally{
    //资源回收
}
在异常处理中,try是必须的,没有try块,后面的catch和finally没有意义,catch和finally必须出现一个,finally块必须是最后。
如果try块中有return语句,则会先执行finally,然后再执行return语句,如果try块中有exit语句,则不会执行finally,都直接退出虚拟机了当然不会再去执行。
2.6 自动关闭的try语句
每次都需要资源回收,显得有些麻烦,Java还支持一种写法,直接在try后申明资源,那么就可以替换掉finally,但这个资源必须实现Closeable或者AutoCloseable接口
try (
BufferedReader bufferedReader = new BufferedReader(new FileReader(""))
) {
bufferedReader.read();
}
三、Checked异常与Runtime异常
Java的异常分为两大类:Checked(可检查)异常和Runtime(运行时)异常,所有的RuntimeException类及其子类的实例就是Runtime异常,其他的都是Checked异常。
对于Checked异常处理方式有两种,一种是明确知道如何处理该异常,用try catch来捕获异常,然后在catch中修复异常,一种是不知道如何处理,在定义方法时申明抛出异常。
Runtime异常无需显示申明抛出,需要捕获异常,就用try catch来实现。
3.1 使用throws声明抛出异常
使用throws声明的思路是:当前方法不知道如何处理这种类型的异常,则由上一级调用者处理,如果main方法也不知道如何处理,也可以使用throws抛给JVM,JVM的处理是,打印异常的跟踪栈信息,并终止程序。
throws声明抛出只能在方法签名中使用,可以声明抛出多个异常类,多个异常类用逗号隔开,如:
public static void main(String[] args) throws IOException {
    FileInputStream fileInputStream = new FileInputStream("");
}
申明了throws就不需要再使用try catch来捕获异常了。
如果某段代码中调用了一个带throws声明的方法,那么必须用try catch来处理或者也带throws声明,如下例子:
public static void main(String[] args) {
    try {
        test();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
public static void test () throws IOException{
    FileInputStream fileInputStream = new FileInputStream("");
}
这个时候要注意,子类方法声明抛出的异常应该是父类方法声明抛出异常的子类活相同,不允许比父类声明抛出的异常多。
本人之前是C#开发,C#开发没有Checked异常,个人觉得这个Checked异常有些繁琐,它最大的作用仿佛是提醒开发处理异常,避免因为粗心而忘记处理异常,不知道是不是理解有误。
四、使用throw抛出异常
程序出现错误,系统会抛出异常,有时候我们也想自行抛出异常,比如用户未登录,我们可能就直接抛出错误,这种自行抛出的异常一般都与业务相关,因为业务数据与既定不符,但是这种异常并不是一种错误,系统不会捕捉,就需要我们自行抛出。
使用throw语句进行异常抛出,抛出的不是一个异常类,而是一个异常实例,而且每次只能抛出一个:
if (user== null) {
    throw new Exception("用户不存在");
}
这里我们又要区分Checked异常与运行时异常,运行时异常申明非常简单,直接抛出即可,而Checked异常又要像之前一样,要么使用try catch,要么声明throws
public static void main(String[] args) {
    try {
        //检查时异常需要写try catch
        test1();
    } catch (Exception e) {
        e.printStackTrace();
    }
    //运行时异常直接调用即可
    test2();
}
public static void test1() throws Exception {
    if (1 > 0) {
        throw new Exception("用户不存在");
    }
}
public static void test2() {
    if (1 > 0) {
        throw new RuntimeException("用户不存在");
    }
}
4.1 自定义异常类
为了让错误更直观的表达信息,有时候我们需要自定义异常, 自定义异常很简单,只需要继承Exception或者RuntimeException基类,一般会提供两个构造器,一个空构造器,一个带有描述信息的构造器:
public class GlobalException extends RuntimeException {
    //无参构造器
    public GlobalException() {
    }
    //带有错误描述信息的构造器
    public GlobalException(String msg) {
        super(msg);
    }
}
4.2 异常链
在实际开发中,我们一般会分层开发,比较常用的是三层,表现出、业务逻辑层、数据库访问层,我们不会抛出数据库异常给用户,因为这些异常中有堆栈信息,很不安全,也非常的不友好。
通常,我们捕获原始异常(可以写入日志),然后再抛出一个业务异常(通常是自定义的异常),这个业务异常可以提示用户异常的原因:
public void update() throws GlobalException{
    try{
        //执行sql
    }
    catch (SQLException ex){
        //记录日志
        ...
            //抛出自定义错误
            throw new GlobalException("数据库报错");
    }
    catch (Exception ex){
        //记录日志
        ...
            throw new GlobalException("未知错误!");
    }
}
这种捕获一个异常然后抛出另一个异常,并将原始信息保存起来的是一种典型的链式处理(责任链模式)。
五、异常处理规则
异常给系统带来了健壮性和容错性,但是使用异常处理并非如此简单,我们还要注意性能和结构的优化,有些规则我们必须了解,而这些规则的主要目标是:
- 程序代码混乱最小化。 
- 捕获并保留诊断信息。 
- 通知合适的人员 
- 采用合适的方式结束异常。 
5.1 不要过度使用异常
什么叫过度使用异常呢?有两种情况,一是把异常和普通错误放在一起,使用异常来代替错误,什么意思呢?就是对一些我们已知或可控的错误进行异常处理,如一些业务逻辑判断,用户的输入等,并不是只有直接抛出异常这种选择,我们可以直接通过业务处理进行错误返回,而不是抛出错误,抛出错误的效率要低一些,只有对外部的、不能确定和预知的运行时错误使用异常。
二就是使用异常来代替流程控制,异常处理机制的初衷是将不可以预期的错误和正常的业务代码分离,不应该用异常来进行流程控制。
5.2 不要使用过大的try块
不要把大量的业务代码放在try中,大量的业务代码意味着错误可能性也增大,也意味着一旦出错,分析错误的复杂度也增加,而且try中包含大量业务,可能后面紧跟的catch块也很多,我们会使用多个catch来捕获错误,这样代码也很臃肿,应该尽量细分try,去分别捕获并处理。
5.3 不要忽略捕获到的异常
不要忽略异常,当我们捕获到异常时,我们不要去忽略它,如果在catch中什么也不做,那是一种恐怖的做法,因为这意味着出现了错误我们并不知道(极特殊的情况例外,比如:一些可重试的业务处理),最起码的做法是打印错误日志,更进一步看是否可以修复错误,或者向上抛出错误。
Java核心技术梳理-异常处理的更多相关文章
- Java核心技术梳理-集合
		一.前言 在日常开发中,我们经常会碰到需要在运行时才知道对象个数的情况,这种情况不能使用数组,因为数组是固定数量的,这个时候我们就会使用集合,因为集合可以存储数量不确定的对象. 集合类是特别有用的工具 ... 
- Java核心技术梳理-基础类库
		一.引言 Oracle为Java提供了丰富的基础类库,Java 8 提供了4000多个基础类库,熟练掌握这些基础类库可以提高我们的开发效率,当然,记住所有的API是不可能也没必要的,我们可以通过API ... 
- Java核心技术梳理-泛型
		一.引言 在学习集合的时候我们会发现一个问题,将一个对象丢到集合中后,集合并不记住对象的类型,统统都当做Object处理,这样我们取出来再使用时就得强制转换类型,导致代码臃肿,而且加入集合时都是以Ob ... 
- Java核心技术梳理-类加载机制与反射
		一.引言 反射机制是一个非常好用的机制,C#和Java中都有反射,反射机制简单来说就是在程序运行状态时,对于任意一个类,能够知道这个类的所有属性和方法,对于任意一个对象,能够调用它的任意属性和方法,其 ... 
- Java核心技术梳理-IO
		一.引言 IO(输入/输出),输入是指允许程序读取外部数据(包括来自磁盘.光盘等存储设备的数据).用户输入数据.输出是指允许程序记录运行状态,将程序数据输出到磁盘.光盘等存储设备中. IO的主要内容包 ... 
- 杨晓峰-Java核心技术-6 动态代理 反射 MD
		目录 第6讲 | 动态代理是基于什么原理? 典型回答 考点分析 知识扩展 反射机制及其演进 动态代理 精选留言 Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAnd ... 
- Java核心技术点之泛型
		1. Why ——引入泛型机制的原因 假如我们想要实现一个String数组,并且要求它可以动态改变大小,这时我们都会想到用ArrayList来聚合String对象.然而,过了一阵,我们想要实现一个大小 ... 
- Java核心技术点之集合框架
		1. 概述 Java集合框架由Java类库的一系列接口.抽象类以及具体实现类组成.我们这里所说的集合就是把一组对象组织到一起,然后再根据不同的需求操纵这些数据.集合类型就是容纳这些对象的一个容 ... 
- 杨晓峰-Java核心技术-9 HashMap Hashtable TreeMap MD
		目录 第9讲 | 对比Hashtable.HashMap.TreeMap有什么不同? 典型回答 考点分析 知识扩展 Map 整体结构 有序 Map HashMap 源码分析 容量.负载因子和树化 精选 ... 
随机推荐
- UICollectionView 具体解说学习
			UICollectionView 和UITableView非常像,是APPLE公司在iOS 6后推出的用于处理图片这类UITableView 布局困难的控件,和UITableView 一样,它也有自己 ... 
- Oracle 模糊查询方法
			在这个信息量剧增的时代,怎样帮助用户从海量数据中检索到想要的数据.模糊查询是不可缺少的. 那么在Oracle中模糊查询是怎样实现的呢? 一.我们能够在where子句中使用likeke ... 
- HDU OJ u Calculate e
			这是一道简单的数学计算问题 主义好输出格式就好 #include<stdio.h> int main() { printf("n e\n- -----------\n&quo ... 
- jsp引用JSTL核心标签库
			一.引用JSTL 1. JSTL的引入可以让JSP代码中<%%>等代码消失掉,再结合EL表达式,会更加方便以及美观. 2. 各套框架(还没有学习,比如struts,SpringMVC等 ... 
- JAVA注解引发的思考
			自从JDK5開始Java開始添加了对元数据(MetaData)的支持,也就是注解(Annotation),到JDK7时已经有四种基本注解,新添加了一种@SafeVarargs. @Override注解 ... 
- 错误	1	无法将程序集“NBear.Data.dll”复制到文件“D:\newbpm\bpm\SureBpm\Bin\NBear.Data.dll”。无法将“D:\newbpm\bpm\SureSoft.WebServiceBaseLib\bin\Debug\NBear.Data.dll”添加到网站。 无法添加文件“Bin\NBear.Data.dll”。 拒绝访问。 	D:\..
			错误 1 无法将程序集“NBear.Data.dll”复制到文件“D:\newbpm\bpm\SureBpm\Bin\NBear.Data.dll”.无法将“D:\newbpm\bpm\SureSof ... 
- scrapy框架的解析
			1,scrapy框架的官网:https://scrapy.org/ 什么是scrapy框架: scrapy 是一个为了爬取网站数据,提取结构性数据而编写的应用内框架,非常出名,所谓框架就是一个已经继承 ... 
- Java 解析excel2003和2007区别和兼容性问题(POI操作)
			最近在使用POI对excel操作中发现一些问题,2003和2007的区别还是蛮大的: 2007相关的包: poi-3.9.jar poi-examples-3.8.jar poi-excelant-3 ... 
- mondb08---导入导出
			//Mongodb数据的导入导出 : 导入/导出可以操作的是本地的mongodb服务器,也可以是远程的. 所以,都有如下通用选项:(本地机就不用这个了) -h host 主机 --port port ... 
- YTU 2851: 数字游戏
			2851: 数字游戏 时间限制: 1 Sec 内存限制: 128 MB 提交: 164 解决: 85 题目描述 输入若干个正整数,将其中能写成其它两个正整数的平方和的数输出来. 例,若输入的数中有 ... 
