Java 异常处理机制的核心是通过 try-catch-finally、throw、throws 等关键字来捕获、抛出和处理程序运行时的异常,确保程序的健壮性。以下是基于你提供的代码清单(listing 1-16)对 Java 异常处理关键概念的总结。
1. 异常的产生与默认处理
当异常发生时,若未被捕获,Java 运行时系统会终止程序并打印异常堆栈信息。
java
// listing 1: 未捕获的异常导致程序终止
class Exc0 {
public static void main(String args[]) {
int d = 0;
int a = 42 / d; // 抛出 ArithmeticException,程序终止 }
}
java
// listing 2: 异常在方法调用栈中传播
class Exc1 {
static void subroutine() {
int d = 0;
int a = 10 / d; // 抛出 ArithmeticException }
public static void main(String args[]) {
Exc1.subroutine(); // 异常从 subroutine 传播到 main,最终程序终止
}
}
2. 使用 try-catch 捕获和处理异常
try 块用于监控可能抛出异常的代码,catch 块用于捕获并处理特定类型的异常。
java
// listing 3: 基本的 try-catch 结构
class Exc2 {
public static void main(String args[]) {
int d, a;
try {
d = 0;
a = 42 / d; // 抛出 ArithmeticException
System.out.println("This will not be printed.");
} catch (ArithmeticException e) { // 捕获算术异常
System.out.println("Division by zero.");
}
System.out.println("After catch statement."); // 异常处理后,程序继续执行 }
}
java
// listing 4: 在循环中处理异常并继续执行
import java.util.Random;
class HandleError {
public static void main(String args[]) {
int a=0, b=0, c=0;
Random r = new Random();
for(int i=0; i<32000; i++) {
try {
b = r.nextInt();
c = r.nextInt();
a = 12345 / (b/c); // 可能除零
} catch (ArithmeticException e) {
System.out.println("Division by zero.");
a = 0; // 恢复状态,继续循环 }
System.out.println("a: " + a);
}
}
}
3. 捕获多个异常
一个 try 块可以跟随多个 catch 块,以处理不同类型的异常。catch 块的顺序必须是从具体(子类)到一般(父类)。
java
// listing 6: 多个 catch 语句
class MultipleCatches {
public static void main(String args[]) {
try {
int a = args.length;
System.out.println("a = " + a);
int b = 42 / a; // 可能抛出 ArithmeticException int c[] = { 1 };
c[42] = 99; // 可能抛出 ArrayIndexOutOfBoundsException
} catch(ArithmeticException e) {
System.out.println("Divide by 0: " + e);
} catch(ArrayIndexOutOfBoundsException e) {
System.out.println("Array index oob: " + e);
}
System.out.println("After try/catch blocks.");
}
}
java
// listing 7: 错误的 catch 顺序导致编译错误
class SuperSubCatch {
public static void main(String args[]) {
try {
int a = 0;
int b = 42 / a;
} catch(Exception e) { // 父类 Exception System.out.println("Generic Exception catch.");
}
catch(ArithmeticException e) { // 子类 ArithmeticException,此块永远无法到达,编译错误 System.out.println("This is never reached.");
}
}
}
4. 嵌套的 try 块
try 块可以嵌套,内层 try 块的异常若未被其自身的 catch 捕获,会向外层传播。
java
// listing 8: 显式嵌套的 try 语句
class NestTry {
public static void main(String args[]) {
try {
int a = args.length;
int b = 42 / a; // 外层可能除零
System.out.println("a = " + a);
try { // 嵌套的 try 块
if(a==1) a = a/(a-a); // 内层可能除零 if(a==2) {
int c[] = { 1 };
c[42] = 99; // 内层可能数组越界 }
} catch(ArrayIndexOutOfBoundsException e) {
System.out.println("Array index out-of-bounds: " + e); // 只捕获内层的数组越界异常 }
} catch(ArithmeticException e) {
System.out.println("Divide by 0: " + e); // 捕获外层和内层传播出来的算术异常 }
}
}
java
// listing 9: 通过方法调用隐式嵌套 try 块
class MethNestTry {
static void nesttry(int a) {
try {
if(a==1) a = a/(a-a);
if(a==2) {
int c[] = { 1 };
c[42] = 99;
}
} catch(ArrayIndexOutOfBoundsException e) {
System.out.println("Array index out-of-bounds: " + e);
}
}
public static void main(String args[]) {
try {
int a = args.length;
int b = 42 / a;
System.out.println("a = " + a);
nesttry(a); // 方法调用构成了隐式的嵌套 try块
} catch(ArithmeticException e) {
System.out.println("Divide by 0: " + e);
}
}
}
5. 抛出异常 (throw) 与声明异常 (throws)
使用 throw 关键字主动抛出一个异常对象。如果一个方法可能抛出受检异常 (checked exception) 且不打算在方法内部处理,必须使用 throws 子句在方法签名中声明。
java
// listing 10: 使用 throw 抛出和重新抛出异常
class ThrowDemo {
static void demoproc() {
try {
throw new NullPointerException("demo"); // 主动抛出异常
} catch(NullPointerException e) {
System.out.println("Caught inside demoproc.");
throw e; // 重新抛出捕获到的异常 }
}
public static void main(String args[]) {
try {
demoproc();
} catch(NullPointerException e) {
System.out.println("Recaught: " + e); // 捕获从 demoproc 重新抛出的异常 }
}
}
java
// listing 11 & 12: throws 声明受检异常
// listing 11 (错误): 未声明 IllegalAccessException,导致编译错误
class ThrowsDemo {
static void throwOne() {
System.out.println("Inside throwOne.");
throw new IllegalAccessException("demo"); // 编译错误:必须声明或捕获 }
public static void main(String args[]) {
throwOne();
}
}
// listing 12 (正确): 使用 throws 声明异常
class ThrowsDemo {
static void throwOne() throws IllegalAccessException { // 声明可能抛出的异常
System.out.println("Inside throwOne.");
throw new IllegalAccessException("demo");
}
public static void main(String args[]) {
try {
throwOne(); // 调用者必须处理或继续声明此异常
} catch (IllegalAccessException e) {
System.out.println("Caught " + e);
}
}
}
6. finally 块
finally 块中的代码无论是否发生异常,都会执行。常用于释放资源(如关闭文件、网络连接)。
java
// listing 13: finally 块的执行
class FinallyDemo {
static void procA() {
try {
System.out.println("inside procA");
throw new RuntimeException("demo");
} finally {
System.out.println("procA's finally"); // 即使抛出异常,也会执行
}
}
static void procB() {
try {
System.out.println("inside procB");
return; // 在 return 之前执行 finally } finally {
System.out.println("procB's finally");
}
}
static void procC() {
try {
System.out.println("inside procC");
} finally {
System.out.println("procC's finally"); // 无异常,正常执行 }
}
public static void main(String args[]) {
try {
procA();
} catch (Exception e) {
System.out.println("Exception caught");
}
procB();
procC();
}
}
/* 输出:
inside procA
procA's finally
Exception caught
inside procB
procB's finally
inside procC
procC's finally
*/
7. 自定义异常
通过继承 Exception类(受检异常)或 RuntimeException 类(非受检异常)来创建自定义异常。
java
// listing 14: 创建和使用自定义异常
class MyException extends Exception { // 自定义受检异常 private int detail;
MyException(int a) {
detail = a;
}
public String toString() {
return "MyException[" + detail + "]";
}
}
class ExceptionDemo {
static void compute(int a) throws MyException { // 声明抛出自定义异常 System.out.println("Called compute(" + a + ")");
if(a > 10)
throw new MyException(a); // 抛出自定义异常
System.out.println("Normal exit");
}
public static void main(String args[]) {
try {
compute(1);
compute(20); // 触发异常
} catch (MyException e) {
System.out.println("Caught " + e); // 捕获并处理自定义异常
}
}
}
8. 异常链 (Chained Exceptions) 与多重捕获 (Multi-catch)
- 异常链:允许将一个异常设置为另一个异常的原因(cause),便于追踪根本原因。
- 多重捕获 (Java 7+) :一个
catch块可以捕获多种类型的异常,使用|分隔。
java
// listing 15: 异常链
class ChainExcDemo {
static void demoproc() {
NullPointerException e = new NullPointerException("top layer");
e.initCause(new ArithmeticException("cause")); // 设置根本原因
throw e;
}
public static void main(String args[]) {
try {
demoproc();
} catch(NullPointerException e) {
System.out.println("Caught: " + e);
System.out.println("Original cause: " + e.getCause()); // 获取原因 }
}
}
java
// listing 16: 多重捕获 (Multi-catch)
class MultiCatch {
public static void main(String args[]) {
int a=10, b=0;
int vals[] = { 1, 2, 3 };
try {
int result = a / b; // 抛出 ArithmeticException
// vals[10] = 19; // 若取消注释,则可能抛出 ArrayIndexOutOfBoundsException
} catch(ArithmeticException | ArrayIndexOutOfBoundsException e) { // 一个catch捕获两种异常 System.out.println("Exception caught: " + e);
}
System.out.println("After multi-catch.");
}
}
关键概念总结表| 概念 | 关键字/结构 | 说明与用途 |
| :--- | :--- | :--- |
| 捕获异常 | try-catch | 将可能出错的代码放在 try 块中,用 catch 块捕获并处理特定异常。 |
| 捕获多个异常 | 多个 catch 块 | 按顺序匹配异常类型,子类异常必须写在父类异常之前。 |
| 最终清理 | finally | 无论是否发生异常,都会执行的代码块,用于释放资源。 |
| 主动抛出 | throw | 在方法体内主动抛出一个异常对象(new ExceptionType())。 |
| 声明异常 | throws | 在方法签名中声明可能抛出的受检异常,将处理责任交给调用者。 |
| 自定义异常 | 继承 Exception | 创建符合特定业务逻辑的异常类型。 |
| 异常链 | initCause(), getCause() | 关联异常,保留根本原因信息。 |
| 多重捕获 | catch (Type1 | Type2 e) | (Java 7+) 简化对多种异常进行相同处理的代码。 |