一篇读懂 Exception 和 Error

前言

我们写 Java 程序经常会出现两种问题,一种是 java.lang.Exception ,一种是 java.lang.Error,都用来表示出现了异常情况,下面就针对这两种概念进行理解。

一篇读懂 Exception 和 Error

Exception

Exception 位于 java.lang 包下,它是一种顶级接口,继承于 Throwable 类,Exception 类及其子类都是 Throwable 的组成条件,是程序出现的合理情况。

在认识 Exception 之前,有必要先了解一下什么是 Throwable。

Throwable

Throwable 类是 Java 语言中所有错误(errors)和异常(exceptions)的父类。只有继承于 Throwable 的类或者其子类才能够被抛出,还有一种方式是带有 Java 中的 @throw 注解的类也可以抛出。

在Java规范中,对非受查异常和受查异常的定义是这样的:

The unchecked exception classes are the run-time exception classes and the error classes.

The checked exception classes are all exception classes other than the unchecked exception classes. That is, the checked exception classes are Throwable and all its subclasses other than RuntimeException and its subclasses and Errorand its subclasses.

也就是说,除了 RuntimeException 和其子类,以及error和其子类,其它的所有异常都是 checkedException。

那么,按照这种逻辑关系,我们可以对 Throwable 及其子类进行归类分析

一篇读懂 Exception 和 Error


可以看到,Throwable 位于异常和错误的最顶层,我们查看 Throwable 类中发现它的方法和属性有很多,我们只讨论其中几个比较常用的

// 返回抛出异常的详细信息

public string getMessage();

public string getLocalizedMessage();

//返回异常发生时的简要描述

public public String toString();

// 打印异常信息到标准输出流上

public void printStackTrace();

public void printStackTrace(PrintStream s);

public void printStackTrace(PrintWriter s)

// 记录栈帧的的当前状态

public synchronized Throwable fillInStackTrace();

复制代码

此外,因为 Throwable 的父类也是 Object,所以常用的方法还有继承其父类的getClass() 和 getName() 方法。

常见的 Exception

下面我们回到 Exception 的探讨上来,现在你知道了 Exception 的父类是 Throwable,并且 Exception 有两种异常,一种是 RuntimeException ;一种是 CheckedException,这两种异常都应该去捕获。

下面列出了一些 Java 中常见的异常及其分类,这块面试官也可能让你举出几个常见的异常情况并将其分类

RuntimeException

序号 异常名称 异常描述 1 ArrayIndexOutOfBoundsException 数组越界异常 2 NullPointerException 空指针异常 3 IllegalArgumentException 非法参数异常 4 NegativeArraySizeException 数组长度为负异常 5 IllegalStateException 非法状态异常 6 ClassCastException 类型转换异常

UncheckedException

序号 异常名称 异常描述 1 NoSuchFieldException 表示该类没有指定名称抛出来的异常 2 NoSuchMethodException 表示该类没有指定方法抛出来的异常 3 IllegalAccessException 不允许访问某个类的异常 4 ClassNotFoundException 类没有找到抛出异常

与 Exception 有关的 Java 关键字

那么 Java 中是如何处理这些异常的呢?在 Java 中有这几个关键字 throws、throw、try、finally、catch

下面我们分别来探讨一下

throws 和 throw

在 Java 中,异常也就是一个对象,它能够被程序员自定义抛出或者应用程序抛出,必须借助于 throws 和 throw 语句来定义抛出异常。

throws 和 throw 通常是成对出现的,例如

static void cacheException() throws Exception{

throw new Exception();

}

复制代码

throw 语句用在方法体内,表示抛出异常,由方法体内的语句处理。 throws 语句用在方法声明后面,表示再抛出异常,由该方法的调用者来处理。

throws 主要是声明这个方法会抛出这种类型的异常,使它的调用者知道要捕获这个异常。 throw 是具体向外抛异常的动作,所以它是抛出一个异常实例。

try 、finally 、catch

这三个关键字主要有下面几种组合方式 try...catch 、try...finally、try...catch...finally

try...catch 表示对某一段代码可能抛出异常进行的捕获,如下

static void cacheException() throws Exception{

try {

System.out.println("1");

}catch (Exception e){

e.printStackTrace();

}

}

复制代码

try...finally 表示对一段代码不管执行情况如何,都会走 finally 中的代码

static void cacheException() throws Exception{

for (int i = 0; i < 5; i++) {

System.out.println("enter: i=" + i);

try {

System.out.println("execute: i=" + i);

continue;

} finally {

System.out.println("leave: i=" + i);

}

}

}

复制代码

try...catch...finally 也是一样的,表示对异常捕获后,再走 finally 中的代码逻辑。

JDK1.7 使用 try...with...resources 优雅关闭资源

Java 类库中有许多资源需要通过 close 方法进行关闭。比如 InputStream、OutputStream,数据库连接对象 Connection,MyBatis 中的 SqlSession 会话等。作为开发人员经常会忽略掉资源的关闭方法,导致内存泄漏。

根据经验,try-finally语句是确保资源会被关闭的最佳方法,就算异常或者返回也一样。try-catch-finally 一般是这样来用的

static String firstLineOfFile(String path) throws IOException {

BufferedReader br = new BufferedReader(new FileReader(path));

try {

return br.readLine();

}finally {

br.close();

}

}

复制代码

这样看起来代码还是比较整洁,但是当我们添加第二个需要关闭的资源的时候,就像下面这样

static void copy(String src,String dst) throws Exception{

InputStream is = new FileInputStream(src);

try {

OutputStream os = new FileOutputStream(dst);

try {

byte[] buf = new byte[100];

int n;

while ((n = is.read()) >= 0){

os.write(buf,n,0);

}

}finally {

os.close();

}

}finally {

is.close();

}

}

复制代码

这样感觉这个方法已经变得臃肿起来了。

而且这种写法也存在诸多问题,即使 try - finally 能够正确关闭资源,但是它不能阻止异常的抛出,因为 try 和 finally 块中都可能有异常的发生。

比如说你正在读取的时候硬盘损坏,这个时候你就无法读取文件和关闭资源了,此时会抛出两个异常。但是在这种情况下,第二个异常会抹掉第一个异常。在异常堆栈中也无法找到第一个异常的记录,怎么办,难道像这样来捕捉异常么?

static void tryThrowException(String path) throws Exception {

BufferedReader br = new BufferedReader(new FileReader(path));

try {

String s = br.readLine();

System.out.println("s = " + s);

}catch (Exception e){

e.printStackTrace();

}finally {

try {

br.close();

}catch (Exception e){

e.printStackTrace();

}finally {

br.close();

}

}

}

复制代码

这种写法,虽然能解决异常抛出的问题,但是各种 try-cath-finally 的嵌套会让代码变得非常臃肿。

Java7 中引入了try-with-resources 语句时,所有这些问题都能得到解决。要使用 try-with-resources 语句,首先要实现 AutoCloseable 接口,此接口包含了单个返回的 close 方法。Java 类库与三方类库中的许多类和接口,现在都实现或者扩展了 AutoCloseable 接口。如果编写了一个类,它代表的是必须关闭的资源,那么这个类应该实现 AutoCloseable 接口。

java 引入了 try-with-resources 声明,将 try-catch-finally 简化为 try-catch,这其实是一种语法糖,在编译时会进行转化为 try-catch-finally 语句。

下面是使用 try-with-resources 的第一个范例

/**

* 使用try-with-resources 改写示例一

* @param path

* @return

* @throws IOException

*/

static String firstLineOfFileAutoClose(String path) throws IOException {

try(BufferedReader br = new BufferedReader(new FileReader(path))){

return br.readLine();

}

}

复制代码

使用 try-with-resources 改写程序的第二个示例

static void copyAutoClose(String src,String dst) throws IOException{

try(InputStream in = new FileInputStream(src);

OutputStream os = new FileOutputStream(dst)){

byte[] buf = new byte[1000];

int n;

while ((n = in.read(buf)) >= 0){

os.write(buf,0,n);

}

}

}

复制代码

使用 try-with-resources 不仅使代码变得通俗易懂,也更容易诊断。以firstLineOfFileAutoClose方法为例,如果调用 readLine()和 close() 方法都抛出异常,后一个异常就会被禁止,以保留第一个异常。

什么是 Error

Error 是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。这些错误是不可检查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况,比如 OutOfMemoryError 和 StackOverflowError异常的出现会有几种情况,这里需要先介绍一下 Java 内存模型 JDK1.7。

一篇读懂 Exception 和 Error

其中包括两部分,由所有线程共享的数据区和线程隔离的数据区组成,在上面的 Java 内存模型中,只有程序计数器是不会发生 OutOfMemoryError 情况的区域,程序计数器控制着计算机指令的分支、循环、跳转、异常处理和线程恢复,并且程序计数器是每个线程私有的。


分享到:


相關文章: