Java异常处理

Exception 的概念

  • Exception 是在程序运行时打断正常程序流程的异常的情况

为了保证程序的正常运行,Java 专门提供了异常处理机制

异常和错误树

  • Error:很难恢复的严重错误,一般不由程序处理。

  • RuntimeException:程序设计或实现上的问题,如数组越界、空指针等。正常的策略是纠正错误。

  • 其它异常:通常是由环境因素引起的,如文件不存在、无效URL等。
    可以在异常处理中处理,例如提示用户进行正确操作等

异常处理方法:

Java异常处理机制为:抛出异常,捕捉异常

抛出异常:

​ 当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行

异常抛出的方式:

  1. 系统自动抛出异常

    • 当Java虚拟机(JVM)在执行代码时遇到无法处理的情况(例如除以零、数组越界、空指针引用等),JVM会自动抛出相应的异常。这些被称为“系统异常”或“运行时异常”,它们大多数是RuntimeException及其子类的实例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public void writeList() throws IOException, ArrayIndexOutOfBoundsException {
    PrintWriter out = new PrintWriter(new FileWriter("OutFile.txt"));
    for (int i = 0; i < size; i++)
    out.println("Value at: " + i + " = " + victor.elementAt(i));
    out.close();
    }

    ...
    try{
    ListOfNumbersDeclared list = new ListOfNumbersDeclared();
    list.writeList(); // 有可能抛出异常
    } catch(Exception e) {System.err.println("Caught Exception");
    }

  2. 使用 throw 关键字手动抛出异常

    • 开发者可以使用 throw 关键字来手动抛出异常,以便在遇到非正常条件时通知调用者。这些异常既可以是Java内置的异常类型,也可以是自定义的异常类型
    • 注意 throws 关键字用于申明类的时候使用,throw 关键字用于内部抛出的时候使用
    1
    2
    3
    4
    5
    6
    public void divide(int divisor) throws ArithmeticException {
    if (divisor == 0) {
    throw new ArithmeticException("Divisor cannot be zero"); // 手动抛出异常
    }
    // 正常的除法运算
    }

捕获异常

  • 在方法抛出异常之后,运行时系统将寻找合适的异常处理器(父类异常处理器可以处理子类处理器的异常)
  • 潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适的异常处理器(可以有多个异常处理器)
  • 运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器并执行。若系统遍历调用栈而未找到合适的异常处理器,则运行时系统终止(比如一直向上抛出Exception,甚至main函数也向上抛)。同时Java程序的终止

Java通过try-catch-finally结构来实现这一功能

  • try

    • try 块包裹了可能抛出异常的代码段。如果在这段代码中发生了异常(比如空指针异常、数组越界异常、IO异常等),程序会立即停止执行该块中的剩余代码,并试图寻找合适的异常处理器(即相应的catch块)
  • catch

    • 紧跟在try块之后的是一个或多个catch块,每个catch块用于捕获并处理特定类型的异常。当异常从try块抛出时,Java会依次catch块能否处理这个异常类型。如果找到匹配项,则执行对应的catch块中的代码
    • 因此catch块内容应给先写异常类子类的,具体的异常;后写父类的异常(Exception写在最后面)
    • 由于异常抛出并被 catch 后不会继续运行,因此多个 catch 块只会处理一个,即处理最先被抛出的异常
  • finally

    • finally 块是可选的,但它总是会在trycatch块完成后执行,无论是否抛出异常,finally块中的代码都会被执行。这对于清理资源(如关闭打开的文件流、数据库连接等)非常有用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
try {
// 这里是可能出现异常的代码
int x = 10 / 0; // 这将引发 ArithmeticException
} catch (ArithmeticException e) {
// 处理除以零的异常情况
System.out.println("Divide by zero error occurred.");
e.printStackTrace();
} catch (Exception e) {
// ...
} finally {
// 这里的代码总会被执行
System.out.println("This is always executed");
// 关闭资源,如:closeStream(stream);
}

自定义异常类:

​ 在Java中自定义异常类通常是为了更精确地描述应用程序特有的错误情况,从而提高程序的可读性和维护性。以下是如何在Java中自定义异常类的步骤:

  1. 创建新类: 新建一个类,该类继承自现有的Java异常基类之一。通常有两种选择:

    • 继承自 java.lang.Exception,创建一个检查型异常(Checked Exception),这种异常在编译时必须被捕获或声明抛出
    1
    public class MyCustomException extends Exception {
    • 继承自 java.lang.RuntimeException,创建一个运行时异常(Unchecked Exception),这种异常不需要在方法签名中显式声明,但在运行时未被捕获时会导致程序终止。
    1
    public class MyRuntimeCustomException extends RuntimeException {
  2. 构造方法: 在自定义异常类中至少需要提供一个构造方法,通常包括一个无参数的构造方法以及一个带详细错误信息字符串的构造方法

  3. (可选)添加额外功能: 如果需要,可以增加成员变量存储更多信息,并提供相应的方法访问这些信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class MyCustomException extends Exception {
    private String errorCode;

    public MyCustomException(String message) {
    super(message); // 调用父类带有详细信息的构造器
    }

    // 可以进一步添加构造方法,比如接收原因异常的构造方法
    public MyCustomException(String message, Throwable cause) {
    super(message, cause);
    }

    public String getErrorCode() {
    return errorCode;
    }
    }

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class MyException extends Exception {

MyException( ) {}

MyException(String msg){
super(msg);
}
}

class UsingMyException {
void f() throws MyException {
System.out.println("Throws MyException from f()");
throw new MyException();
}
void g() throws MyException {
System.out.println("Throws MyException from g()");
throw new MyException("Originated in g()");
}
}

public class TestMyException{
public static void main ( String args[] ) {
UsingMyException m = new UsingMyException();

try {
m.f();
} catch ( MyException e ) {
e.printStackTrace();
}

try {
m.g();
} catch ( MyException e ) {
e.printStackTrace();
}

}
}