永远不要期待程序在完全理想的状态下运行,异常往往不期而遇,如果没有完善的异常处理机制,后果可能是灾难性的。对于 Java 工程师而言,合理地处理异常是一种基本而重要的能力,然而,在近来的面试中,笔者发现很多应聘者对异常处理的内在原理几无了解,现场手写的异常处理代码也极为“原始”。
鉴于此,笔者将通过本场 Chat 为读者呈现 Java 异常处理的内在原理、处理原则及优雅的处理方式。主要内容如下:
- Java 异常的层次结构和处理机制
- Java 异常表与异常处理的内在原理
- .Java 异常处理的基本原则
- 优雅地处理 Java 异常案例
- Java 异常简介
对于 Java 工程师而言,异常应该并不陌生,作为本场 Chat 的引入部分,对 Java 异常的基础知识仅作简要回顾,本文主体将聚焦于深入解读 Java 异常的底层原理和异常处理实践。
1.1 Java 异常类层次结构
在 Java 中,所有的异常都是由 Throwable 继承而来,换言之,Throwable 是所有异常类共同的“祖先”,层次结构图如下所示(注:Error、Exception 的子类及其孙子类只列出了部分):
1.2 Java 异常类相关的基本概念
Throwable
作为所有异常类共同的“祖先”,Throwable 在“下一代”即分化为两个分支:Exception(异常)和 Error(错误),二者是 Java 异常处理的重要子类。
Error
Error 类层次结构用于描述 Java 运行时系统的内部错误和资源耗尽错误,这类错误是程序无法处理的严重问题,一旦出现,除了通告给用户并尽可能安全终止程序外,别无他法。
常见的错误如:
JVM 内存资源耗尽时出现的 OutOfMemoryError
栈溢出时出现的 StackOverFlowError
类定义错误 NoClassDefFoundError
这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,它们在应用程序的控制和处理能力之外,一旦发生,Java 虚拟机一般会选择线程终止。对于新手小白想更轻松的学好Java提升,Java架构,web开发、大数据,数据分析,人工智能等技术,这里给大家分享系统教学资源,扩列下我尉(同英):CGMX9880 【教程/工具/方法/解疑】
Exception
相较于 Error,Exception 类层次结构所描述的异常更需要 Java 程序设计者关注,因为它是程序本身可以处理的。Exception 类的“下一代”分化为两个分支:RuntimeException + 其它异常。
划分两个分支的原则为:
由程序错误导致的异常属于 RuntimeException;
而程序本身没有问题,但由于 I/O 错误之类问题导致的异常属于其它异常。
关于异常和错误的区别:通俗地讲,异常是程序本身可以处理的,而错误则是无法处理的。
可检查异常
可检查异常也称为已检查异常(Checked Exception),这类异常是编译器要求必须处置的异常。在工程实践中,程序难免出现异常,其中一些异常是可以预计和容忍的,比如:
读取文件的时候可能出现文件不存在的情况(FileNotFoundException),但是,并不希望因此就导致程序结束,那怎么办呢?
通常采用捕获异常(try-catch)或者抛出异常(throws 抛出,由调用方处理)的方式来处理。
可检查异常虽然也是异常,但它具备一些重要特征:可预计、可容忍、可检查、可处理。因此,一旦发生这类异常,就必须采取某种方式进行处理。
Java 语言规范将派生于 Error 类或 RuntimeException 类之外的所有异常都归类为可检查异常,Java 编译器会检查它,如果不做处理,无法通过编译。
不可检查异常
与可检查异常相反,不可检查异常(Unchecked Exception)是 Java 编译器不强制要求处置的异常。Java 语言规范将 Error 类和 RuntimeException 及其子类归类为不可检查异常。
为什么编译器不强制要求处置呢?不是因为这类异常简单,危害性小,而是因为这类异常是应该尽力避免出现的,而不是出现后再去补救。以 RuntimeException 类及其子类为例:
NullPointerException(空指针异常)
IndexOutOfBoundsException(下标越界异常)
IllegalArgumentException(非法参数异常)
这些异常通常是由不合理的程序设计和不规范的编码引起的,工程师在设计、编写程序时应尽可能避免这类异常的发生,这是可以做到的。在 IT 圈内有个不成文的原则:如果出现 RuntimeException 及其子类异常,那么可认为是程序员的错误。对于新手小白想更轻松的学好Java提升,Java架构,web开发、大数据,数据分析,人工智能等技术,这里给大家分享系统教学资源,扩列下我尉(同英):CGMX9880 【教程/工具/方法/解疑】
1.3 异常处理机制
在 Java 应用程序中,异常处理机制有:抛出异常、捕捉异常。
抛出异常
这里的“抛出异常”是指主动抛出异常。在设计、编写程序时,我们可以预料到一些可能出现的异常,如 FileNotFoundException,有时候我们并不希望在当前方法中对其进行捕获处理,怎么办呢?抛出去,让调用方去处理,通过 throw 关键字即可完成,如:
throw new FileNotFoundException()
关于抛出异常,还有一个点需要补充,那就是声明可检查异常。在设计程序的时候,如果一个方法明确可能发生某些可检查异常,那么,可以在方法的定义中带上这些异常,如此,这个方法的调用方就必须对这些可检查异常进行处理。
声明异常
根据 Java 规范,如果一个 Java 方法要抛出异常,那么需要在这个方法后面用 throws 关键字明确定义可以抛出的异常类型。倘若没有定义,就默认该方法不抛出任何异常。这样的规范决定了 Java 语法必须强行对异常进行 try-catch。如下的方法签名:
public void foo() throws FileNotFoundException { … }
暗含了两方面的意思:
第一,该方法要抛出 FileNotFoundException 类型的异常;
第二,除了 FileNotFoundException 外不能(根据规范)抛出其它的异常。
那么,如何保证没有除 FileNotFoundException 之外的任何异常被抛出呢?很显然,方式有:
通过合理的设计和编码避免出现其它异常;
如果其它异常不可完全避免(如方法内调用的其它方法明确可能出现异常),就需要 try-cat