Java异常体系
Java异常体系是Java编程语言中用于处理程序运行时错误和异常情况的一种结构化方法。它主要包含两大类:Error
和 Exception
。
-
Error:
- Error类及其子类代表了JVM在运行时无法预见也无法恢复的严重错误,通常与系统的底层资源有关,如内存溢出(
OutOfMemoryError
)、系统崩溃(VirtualMachineError
)等。这些错误发生后,应用程序往往无法正常继续执行,因此程序员通常不期望也不需要对这类错误进行捕获和处理。
- Error类及其子类代表了JVM在运行时无法预见也无法恢复的严重错误,通常与系统的底层资源有关,如内存溢出(
-
Exception:
- Exception类及其子类则表示程序在运行过程中可能出现的问题,它们可以被开发者通过代码来捕获并进行适当的处理。
- 运行时异常 (Runtime Exception):也称为未检查异常(Unchecked Exception),继承自
RuntimeException
,例如NullPointerException
、ArrayIndexOutOfBoundsException
等。这类异常在编译阶段不会强制要求程序员进行处理,但建议在编码时避免出现这类异常,因为它们通常是由于逻辑错误或非法操作导致的。 - 非运行时异常 (Checked Exception):所有不是
RuntimeException
或其子类的Exception
都是非运行时异常,如IOException
、SQLException
等。对于这种类型的异常,Java编译器会强制要求程序员在编写代码时要么使用try-catch
块进行捕获处理,要么在方法签名上使用throws
关键字声明该方法可能抛出的异常,让调用者负责处理。
- 运行时异常 (Runtime Exception):也称为未检查异常(Unchecked Exception),继承自
- Exception类及其子类则表示程序在运行过程中可能出现的问题,它们可以被开发者通过代码来捕获并进行适当的处理。
Java异常处理的关键字主要包括:
try
:用于包裹可能会抛出异常的代码块。catch
:用于捕获特定类型的异常,并在捕获到异常时执行相应的处理代码。finally
:无论是否发生异常,都会执行的代码块,常用于资源清理工作。throw
:在方法内部手动抛出一个异常对象。throws
:在方法声明部分列出可能抛出的异常类型列表,表明该方法不处理这些异常,而是将异常处理的责任转交给方法的调用者。
怎么用的呢?
下面通过一个简单的案例来演示Java异常体系的使用:
import java.io.*;
public class ExceptionDemo {
// 该方法读取指定文件的内容,可能会抛出IOException(非运行时异常)
public String readFile(String filePath) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
StringBuilder content = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
content.append(line).append("\n");
}
return content.toString().trim();
}
}
// 主函数中调用readFile方法并处理异常
public static void main(String[] args) {
ExceptionDemo demo = new ExceptionDemo();
try {
String fileContent = demo.readFile("non_existent_file.txt"); // 假设这个文件不存在
System.out.println("File content: " + fileContent);
} catch (FileNotFoundException e) {
System.out.println("File not found: " + e.getMessage());
// 对找不到文件的异常进行处理
} catch (IOException e) {
System.out.println("An I/O error occurred: " + e.getMessage());
// 对其他I/O异常进行处理
} finally {
System.out.println("This block is always executed, no matter an exception occurs or not.");
// 这个块用于资源清理或其他无论是否发生异常都需要执行的操作
}
}
}
在这个例子中:
-
readFile
方法尝试打开并读取一个文件的内容,这可能抛出IOException
,因为文件可能不存在、无法读取或有其他I/O错误。由于这是非运行时异常,所以在定义方法时需要声明throws IOException
。 -
在
main
方法中调用readFile
时,我们使用了try-catch
结构来捕获和处理可能出现的异常。当出现FileNotFoundException
(继承自IOException
)时,程序会执行对应的catch
块中的代码;如果出现其他类型的IOException
,则执行另一个catch
块。 -
finally
块中的代码无论是否发生异常都会被执行,通常用来做资源清理工作,例如关闭流对象等。在上述示例中,虽然我们使用了try-with-resources语句(即try (BufferedReader reader = ...)
),它会在结束块后自动关闭reader,但在实际更复杂的场景中,finally
块依然有着重要作用。
finally 一定执行吗?
finally
块中的代码在大多数情况下都会执行,它设计的初衷就是为了确保某些资源清理或其他必须执行的操作无论是否发生异常都能够得到执行。以下情况finally
块会执行:
- 当try块正常执行完毕后。
- 当try块中抛出了一个异常,并且该异常没有在任何catch块中被捕获(或者有匹配的catch块并已执行)。
- 在try或catch块中使用了
return
语句时,finally块会在方法返回之前被执行。
然而,有一种特殊的情况会导致finally块不执行:
- 如果在
finally
块中有System.exit(int)
方法调用,那么程序会立即终止运行,不再执行finally块之后的任何代码。
另外,在Java 8及以上版本中,如果在主线程中执行了Thread.stop()
、System.gc().addShutdownHook()
并且虚拟机在关闭过程中,也可能导致finally块无法执行。但这些情况相对罕见,通常开发中我们并不推荐使用这些操作。总的来说,正常情况下,finally块总是会被执行的。
try,catch,finally常用的组合
在Java中,try-catch-finally
结构是一种常见的异常处理方式,它们的组合可以有以下几种常见形式:
-
基本形式:try-catch
try { // 可能抛出异常的代码块 } catch (ExceptionType1 e) { // 处理ExceptionType1类型的异常 }
这是最简单的形式,仅用于捕获并处理一个特定类型的异常。
-
多个catch块:try-catch-catch
try { // 可能抛出异常的代码块 } catch (ExceptionType1 e) { // 处理ExceptionType1类型的异常 } catch (ExceptionType2 e) { // 处理ExceptionType2类型的异常 }
当需要处理多种不同类型的异常时,可以使用多个catch块。注意,catch块的顺序很重要,JVM会从上至下匹配并执行第一个能够捕获到异常的catch块。
-
结合finally:try-catch-finally
try { // 可能抛出异常的代码块 } catch (ExceptionType e) { // 处理ExceptionType类型的异常 } finally { // 无论是否发生异常都会执行的代码块(如资源清理) }
finally块通常用于释放资源(如关闭文件流、数据库连接等),即使在try或catch块中有return语句,finally块也会在方法返回之前被执行。
-
同时处理多种异常且包含finally:try-catch-catch-finally
try { // 可能抛出异常的代码块 } catch (ExceptionType1 e) { // 处理ExceptionType1类型的异常 } catch (ExceptionType2 e) { // 处理ExceptionType2类型的异常 } finally { // 无论是否发生异常都会执行的代码块(如资源清理) }
这种形式结合了上述两种情况,既处理多种异常,又有finally块确保必要的清理操作。
-
try-finally是Java异常处理中的另一种常见组合,它用于确保在程序执行过程中无论是否发生异常,finally块中的代码都将被执行。这种结构主要用于资源清理等必须进行的操作。
try {
// 可能抛出异常的代码块
// 如打开文件、数据库连接等操作
} finally {
// 无论是否发生异常都会执行的代码块(如关闭文件流、关闭数据库连接)
// 这里进行必要的资源清理工作
}
例如:
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
// 读取文件内容的代码...
} finally {
System.out.println("Finally block executed, this message is always printed.");
}
在这个例子中,try-with-resources
语句会自动管理BufferedReader
对象的关闭,即使在读取文件时发生异常,也会确保在退出try块之前关闭reader。但假设我们没有使用try-with-resources
,那么finally块可以用来手动关闭文件流以防止资源泄漏。
throw和throws
throw
和throws
关键字都与异常处理机制相关,但它们的作用不同:
-
throw:
throw
用于在程序中手动抛出一个异常对象。当满足某个错误条件时,可以在方法内部使用throw
语句抛出一个已存在的异常实例或者自定义的异常实例。
if (condition) { throw new IllegalArgumentException("Invalid argument"); }
上述代码表示如果条件为真,则抛出一个
IllegalArgumentException
异常。throws: -
throws
关键字用在方法声明上,用来表明该方法可能会抛出的异常类型。调用该方法的代码必须处理这些可能抛出的异常,通常是通过在其调用处添加try-catch
块或在调用方法的方法上再次声明throws
。public void readFile(String filePath) throws IOException { // 方法体,可能会抛出IOException } // 在其他方法中调用readFile方法并处理异常 public void processFile() { try { readFile("file.txt"); } catch (IOException e) { // 处理IOException } }
或者
public void anotherMethod() throws IOException { readFile("file.txt"); // 将异常处理的责任传递给anotherMethod的调用者 }
throw
是在程序运行时主动抛出一个异常,而throws
是声明一个方法可能会抛出的异常类型,需要调用者进行处理。
自定义异常
根据实际需求自定义异常类。自定义异常通常继承自已有的异常类,以便更好地描述和处理特定的错误情况。
以下是一个创建自定义异常类的基本步骤:
// 自定义一个名为MyException的异常类,它继承自RuntimeException
public class MyException extends RuntimeException {
// 可以定义私有属性来携带额外的异常信息
private String errorCode;
// 构造函数,用于初始化异常信息
public MyException(String message, String errorCode) {
super(message); // 调用父类(RuntimeException)的构造函数传入错误消息
this.errorCode = errorCode; // 初始化自定义的错误码
}
// 提供获取错误码的方法
public String getErrorCode() {
return errorCode;
}
}
// 在业务逻辑中使用自定义异常
public class SomeService {
public void someMethod(int value) throws MyException {
if (value <= 0) {
throw new MyException("Invalid value: must be positive", "E001");
}
// 正常执行的代码...
}
}
// 主程序中捕获并处理自定义异常
public class Main {
public static void main(String[] args) {
SomeService service = new SomeService();
try {
service.someMethod(-5);
} catch (MyException e) {
System.out.println("Error code: " + e.getErrorCode());
System.out.println("Error message: " + e.getMessage());
}
}
}
在这个例子中,我们首先定义了一个名为MyException
的自定义异常类,它继承自RuntimeException
。然后,在SomeService
类的someMethod
方法中,当遇到无效参数时,抛出这个自定义异常,并附带了错误消息和错误码。最后,在主程序中通过catch
块捕获并处理该自定义异常,从中获取了错误信息。