浅谈Java异常处理:Error与Exception那点事儿

世界上存在永不出错的程序吗?有,不过是在程序员的梦中,自编程语言诞生的那一刻起,程序异常也就一起诞生了,异常处理机制也随之一起出现了。

究竟什么是异常处理,我们以Java语言为例的话,异常就是一个Throwable对象实例,只有Throwable对象才能被捕获和抛出。

Java异常分为两大类:Error和Exception

  • Error
    Error是Java运行时抛出的JVM层面的错误,当它发生后,我们能做的只是将之捕获,然后程序安全退出,别的就无能为力了,如:OutOfMemoryError
  • Exception
    Exception一般是代码错误引起的,应该被捕获并处理,以使程序重回正轨

异常处理简单来说就是捕获Error和Exception,进行相应处理后恢复程序的运行或以适当形式退出程序。

从检查和捕获的角度看,Java异常又可以分为checked异常和unchecked异常

  • unchecked异常
    Error和Exception的子类RuntimeException被称为unchecked异常,这类异常是运行时异常,编译期不做检查,非强制捕获,一般可以通过编码避免的,如:NullPointerException、ArrayIndexOutOfBoundsException等
  • checked异常
    Exception的子类中除了RuntimeException以外的异常被称为checked异常,这类异常在编译期就会被检查,必须被捕获处理,如:IOException、ClassNotFoundException等

为什么进行异常处理?

对于用户来说遇到错误会感觉不爽,影响用户体验,甚至会造成用户数据丢失,那就有可能永远失去这个用户了,为了避免这种情况,至少要做到以下几点:

  • 向用户提供一个友好的错误提示
  • 保存所有操作的结果
  • 允许用户以适当的形式退出程序

要做到这几点并非易事,因为引发错误的代码往往距离做这几件事的代码很远。异常处理的任务就是把控制权由错误产生的地方转移给能够处理这种情况的错误处理器,进行恢复处理或为程序的安全退出提供通道。

在Java语言中程序出现错误时, 程序执行流程会发生改变,控制权将转移到异常处理器,进行恢复处理或者为程序的退出提供安全通道。Java语言在设计之初就提供了完善的异常处理机制,如今异常处理机制已经成为现代编程语言的标配了。

引申问题:NoClassDefFoundError与ClassNotFoundException有什么区别?

  • NoClassDefFoundError是JVM或Class Loader实例尝试加载类时,找不到类的定义,可能的原因包括打包漏了类、jar包损坏或缺失等
  • ClassNotFoundException一般是动态加载类时在类路径里找不到这个类。JVM是支持通过反射的方式在运行时动态加载类的,如:Class.forName方法,将类名作为参数,从而将指定类加载到JVM内存中,如果类路径中找不到这个类,就会报ClassNotFoundException

使用异常机制的建议

  • 不要用异常控制程序执行的流程
try {
    int i = 0;
    while (true) {
        range[i++].climb();
    }
} catch (ArrayIndexOutOfBoundsException e) {
}

上面这段代码目的是循环遍历数组元素,而终止循环的办法就是通过访问数组边界外的第一个元素引起异常被throw、catch,这种通过异常控制代码执行流程的做法是错误的,数组遍历的常规做法是使用for或foreach循环。这种写法错误的原因有二:

  1. 异常是在程序出现不正常的情况下使用的,所以JVM一般不会对try-catch语句块做优化,而对于for或foreach循环,JVM一般会将冗余的检查优化掉
  2. 这种处理方式并不能保证永远正常工作,假设在try-catch块中调用了另外一个方法,在这个方法中使用了for循环遍历一个数组,而且凑巧这个for循环发生了未被捕获的数组越界异常,这个异常会被try-catch块捕获从而引起while循环遍历提前终止
  • 优先使用标准异常

有经验的程序员会避免重复造轮子,Java平台类库已经提供了一组可以满足大部分需要的未检查异常了,我们为什么要自己再去造轮子呢? 重用标准异常主要的好处有以下几点:
1、使用标准异常可以使你的API更易于学习和使用,因为它与别的程序员已经熟悉的习惯用法是一致的
2、使用标准异常会使代码的可读性更好
3、使用标准异常意味着异常类的数量不会大量增加,装载这些异常类的时间开销也不会大幅增长

常用的标准异常有以下几种:
NullPointerException:在禁止使用null的情况下参数值为null
IllegalArgumentException:非null的参数值不正确
IllegalStateException:对于方法调用而言,对象状态不合适
IndexOutOfBoundsException:下标参数值越界
ConcurrentModificationException:在禁止并发修改的情况下,检测到对象的并发修改
UnsupportedOperationException:对象不支持用户请求的方法

当程序中由于未被捕获的异常而失败时,系统会自动打印出异常的堆栈轨迹,这些信息对于程序员调查失败原因是非常重要的线索,如果要定义自己的非标准异常,一定要在异常的细节信息中包含对排查失败原因有帮助的信息,但是这些信息中不要包含相关的用户隐私信息,如:手机号、密码、IP及端口等,一旦日志文件泄露就有用户信息被窃的风险。

  • 不要生吞异常
try {
    //业务代码
} catch (IllegalStateException ignore) {
}

上面业务代码中抛出的异常被捕获后吃掉了,后面的代码可能会以不可控的方式结束,却无法判断出哪里发生了异常,整个世界非常平静,只剩下抓狂的程序员在排查失败原因时不停的挠头,继续摧残仅剩的几根头发。对于被捕获的异常要么打日志记录异常信息要么继续throw出去等待后续处理。

  • Throw early, catch late

下面对比一下两段代码及异常栈信息
代码段一:

public void read(String fileName) throws FileNotFoundException {
    InputStream in = new FileInputStream(fileName);
}

代码段二:

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

对于这两个方法进行相同的调用

  read(null);

再来对比一下两段代码的异常栈信息

 Exception java.lang.NullPointerException
        at FileInputStream.<init> (FileInputStream.java:149)
        at FileInputStream.<init> (FileInputStream.java:112)
        at read (#1:2)
        at (#4:1)
Exception java.lang.NullPointerException
        at Objects.requireNonNull (Objects.java:221)
        at read2 (#6:2)
        at (#7:1)

同样都是NullPointerException异常,显然第二种异常信息更具可读性,更利于定位问题,所以throw early。
假如某一个方法会抛出异常,那是否要在方法内做异常捕获呢?前面提到过Java异常处理是对程序错误进行恢复处理或者为程序的退出提供安全通道,那么在方法内异常捕获以后能确定怎样进行恢复处理或安全退出吗?如果命案是否定的,那就不要捕获异常了,还是继续throw出去吧,所以catch late。

  • 抛出异常的方法要有文档

如果没有为方法可抛出的异常建立文档,则使用者很难正确的使用你的类和接口。对于已检查异常,使用Javadoc的@throws标记记录下抛出异常的条件,并使用throws关键字将异常包含中方法声明中;对于未检查异常,使用Javadoc的@throws标记记录下抛出异常的条件,但是不需要使用throws关键字将异常包含在方法声明中。

  • 生产环境中不要使用printStackTrace
try {
  //业务代码
} catch (IOException ex) {
    ex.printStackTrace();
}

像这样的日志打印方法不宜在生产环境中使用,尤其是在分布式系统中,很难判断日志输出到了哪里,增加了问题排查的难度,建议使用Log4j等日志框架。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容