Java 异常处理
一、引言
Java 的异常处理是其重要特性之一,也是提升代码健壮性的强大手段。编写代码时,编译器在编译期间可能抛出异常,即便编译正常,运行代码时也可能出现异常。下面将详细介绍异常相关知识,包括定义、类架构、处理方式、自定义异常以及异常链等内容。
二、什么是异常
异常即程序中的错误,可分为编译期间错误和运行期间错误。以下是一些常见异常案例:
- 编译期异常:语句漏写分号、关键字拼写错误等,编译器会在编译期间明确提示错误。例如,语句漏写分号时,Java 编译器会指出“需要 ‘;’”。
- 运行期异常:如数组下标越界,程序编译阶段正常,但运行时会抛出
ArrayIndexOutOfBoundsException
异常。
三、Java 异常类架构
在 Java 中,通过 Throwable
及其子类描述各种异常。以下是主要类介绍:
3.1 Throwable 类
Throwable
位于 java.lang
包下,是所有错误(Error
)和异常(Exception
)的父类。它包含线程创建时的堆栈快照,提供了 printStackTrace()
等方法用于获取堆栈跟踪数据。主要方法如下:
fillInStackTrace
:用当前调用栈层次填充Throwable
对象栈层次。getMessage
:返回异常详细信息。getCause
:返回代表异常原因的Throwable
对象。getStackTrace
:返回包含堆栈层次的数组。printStackTrace
:将异常信息和栈层次打印到错误输出流。
3.2 Error 类
Error
是 Throwable
的直接子类,指示应用程序不应尝试捕获的严重问题。这些错误超出应用程序的控制和处理能力,编译器不会检查。常见的 Error
包括 AssertionError
、VirtualMachineError
、UnsupportedClassVersionError
和 OutOfMemoryError
等。
3.3 Exception 类
Exception
也是 Throwable
的直接子类,指示应用程序可能希望捕获的条件,可分为以下两类:
3.3.1 Unchecked Exception(非检查异常)
编译器不要求强制处理,包含 RuntimeException
及其子类。编写代码时不处理此类异常,程序仍可编译通过。常见的非检查异常有 NullPointerException
、ArithmeticException
、ArrayIndexOutOfBoundsException
和 ClassCastException
等。
3.3.2 Checked Exception(检查异常)
编译器要求必须处理,除 RuntimeException
及其子类外的异常都属于此类。编写程序时必须处理,否则无法编译通过。常见的检查异常有 IOException
和 SQLException
等。
四、如何进行异常处理
Java 异常处理机制分为抛出异常和捕获异常两部分,通过 throw
、throws
、try
、catch
、finally
五个关键字实现。异常总是先抛出,后捕获。
4.1 抛出异常
4.1.1 实例
以除零异常为例,若 divide()
方法中除数为 0,程序会停止执行并显示异常信息。
4.1.2 throw
使用 throw
关键字可手动抛出异常,后面跟异常对象。例如,在 divide()
方法中,若除数为 0,可使用 throw new ArithmeticException("除数不能为零")
抛出异常。throw
会中断方法,其后的内容除非处理异常,否则不会执行。
4.1.3 throws
throws
用于方法定义时声明要抛出的异常类型。若方法可能出现异常但无能力处理,可使用 throws
声明。例如:
public void demoMethod() throws Exception1, Exception2,... ExceptionN {
// 可能产生异常的代码
}
throws
后的异常类型列表可包含一个或多个异常,以逗号分隔。当方法产生列表中的异常时,会将异常抛给调用方处理。使用规则如下:
- 若方法中全是非检查异常,可不使用
throws
声明,编译器可通过编译,但运行时会被系统抛出。 - 若方法可能出现检查异常,必须使用
throws
声明抛出或用try-catch
捕获,否则编译错误。 - 方法抛出异常,调用者必须处理或重新抛出。
- 子类重写父类抛出异常的方法时,声明的异常必须是父类所声明异常的同类或子类。
4.2 捕获异常
使用 try
和 catch
关键字捕获异常,try-catch
代码块放在异常可能发生的地方。语法如下:
try {
// 可能会发生异常的代码块
} catch (Exception e1) {
// 捕获并处理try抛出的异常类型Exception
} catch (Exception2 e2) {
// 捕获并处理try抛出的异常类型Exception2
} finally {
// 无论是否发生异常,都将执行的代码块
}
- try 语句块:监听异常,发生异常时抛出。
- catch 语句块:包含要捕获的异常类型声明,按声明顺序从上往下匹配,一旦匹配就不再向下执行。若多个
catch
异常类型有父子关系,子类异常应放在前面,父类异常放在后面。 - finally 语句块:无论是否发生异常都会执行,常用于释放
try
代码块中打开的物理资源,如数据库连接、网络连接和文件等。
try
语句块后可接零个或多个 catch
语句块,若无 catch
块,则必须跟一个 finally
语句块。try
不能单独使用,必须和 catch
或 finally
组合,catch
和 finally
也不能单独使用。
Java 7 以后,catch
多种异常时可简化代码:
try {
// 可能会发生异常的代码块
} catch (Exception | Exception2 e) {
// 捕获并处理try抛出的异常类型
} finally {
// 无论是否发生异常,都将执行的代码块
}
五、自定义异常
自定义异常需定义一个类,继承 Throwable
类或其子类。Java 内置异常类通常可描述大部分异常情况,但当内置异常无法满足业务需求时,可自定义异常。示例如下:
public class ExceptionDemo4 {
static class MyCustomException extends RuntimeException {
public MyCustomException() {
super("我的自定义异常");
}
}
public static void main(String[] args) {
throw new MyCustomException();
}
}
六、异常链
异常链是以一个异常对象为参数构造新的异常对象,将异常信息从底层传递给上层,逐层抛出。示例代码如下:
public class ExceptionDemo6 {
static class FirstCustomException extends Exception {
public FirstCustomException() {
super("第一个异常");
}
}
static class SecondCustomException extends Exception {
public SecondCustomException(Throwable cause) {
super("第二个异常", cause);
}
}
static class ThirdCustomException extends Exception {
public ThirdCustomException(Throwable cause) {
super("第三个异常", cause);
}
}
public static void f1() throws FirstCustomException {
throw new FirstCustomException();
}
public static void f2() throws SecondCustomException {
try {
f1();
} catch (FirstCustomException e) {
throw new SecondCustomException(e);
}
}
public static void f3() throws ThirdCustomException {
try {
f2();
} catch (SecondCustomException e) {
throw new ThirdCustomException(e);
}
}
public static void main(String[] args) throws ThirdCustomException {
f3();
}
}
运行该代码,异常发生的整个过程都会打印到屏幕上,形成异常链。
七、小结
通过学习可知,异常即程序中的错误,良好的异常处理可提高代码健壮性。Java 中所有错误和异常的父类是 Throwable
,异常处理主要针对 Exception
及其子类,可分为检查型异常和非检查型异常。通过抛出和捕获异常实现异常处理,还可继承 Throwable
类或其子类来自定义异常类,利用构造方法获取之前异常的信息实现异常链。