ZetCode

Java 异常类

最后修改时间:2025 年 4 月 13 日

java.lang.Exception 类是 Java 中所有异常的超类。 异常是运行时事件,会中断正常的执行流程并发出信号,表明需要处理这些问题以实现健壮的应用程序行为。 正确的异常管理可确保错误恢复,增强稳定性和改进调试。

Java 异常分为两个主要类别:检查型异常(编译时)和 非检查型异常(运行时)。 检查型异常必须显式处理,可以通过捕获它们或在方法签名中声明它们,而非检查型异常是由于编程错误而发生的,并且可以在没有强制处理的情况下传播。 了解异常类型和处理机制对于编写可靠、可维护的代码至关重要。

异常类层次结构

在 Java 的异常层次结构中,Exception 类直接位于 Throwable 之下。 它有两个关键子类:RuntimeException(代表非检查型异常)和 IOException(一种常见的检查型异常类型)。 这种区别决定了异常在代码中的管理方式。

java.lang.Throwable
├── java.lang.Error
└── java.lang.Exception
    ├── java.lang.RuntimeException
    │   ├── java.lang.NullPointerException
    │   ├── java.lang.IllegalArgumentException
    │   ├── java.lang.IndexOutOfBoundsException
    │   └── ...
    ├── java.lang.IOException
    │   ├── java.lang.FileNotFoundException
    │   ├── java.lang.UnsupportedEncodingException
    │   └── ...
    ├── java.lang.ReflectiveOperationException
    ├── java.lang.InterruptedException
    └── Other checked exceptions

主要区别:检查型与非检查型异常

检查型异常和非检查型异常之间的区别有助于开发人员预测故障点,改进程序流程并确保正确处理意外情况。

基本异常处理

Java 提供了 try-catch 块来处理异常。 try 块包含可能抛出异常的代码,而 catch 块定义了发生异常时如何处理该异常。 这可以防止程序突然终止。

在实践中,许多异常(例如除以零)都是通过在执行操作之前使用条件检查来避免的,而不是依赖于事后捕获它们。 积极主动的方法可以提高程序的稳定性并避免不必要的异常处理。

Main.java
package com.zetcode;

public class Main {

    public static void main(String[] args) {
        try {
            int result = divide(10, 0);
            System.out.println("Result: " + result);
        } catch (ArithmeticException e) {
            System.out.println("Error: " + e.getMessage());
            e.printStackTrace();
        }
    }
    
    public static int divide(int a, int b) {
        return a / b;
    }
}

此示例演示了基本的异常处理。 divide 方法在除以零时抛出 ArithmeticException。 但是,在实际应用中,在执行除法之前检查零将是首选方法,以防止异常发生。

检查型与非检查型异常

检查型异常必须声明或处理,而非检查型异常则不需要。 检查型异常扩展了 Exception 但没有扩展 RuntimeException。 非检查型异常扩展了 RuntimeExceptionError

Main.java
package com.zetcode;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        // Unchecked exception (no need to declare)
        try {
            String str = null;
            System.out.println(str.length());
        } catch (NullPointerException e) {
            System.out.println("NullPointerException caught");
        }
        
        // Checked exception (must be handled)
        try {
            readFile("nonexistent.txt");
        } catch (FileNotFoundException e) {
            System.out.println("File not found: " + e.getMessage());
        }
    }
    
    public static void readFile(String path) throws FileNotFoundException {
        File file = new File(path);
        Scanner scanner = new Scanner(file);
        while (scanner.hasNextLine()) {
            System.out.println(scanner.nextLine());
        }
        scanner.close();
    }
}

此示例对比了检查型异常和非检查型异常。 NullPointerException 是非检查型的,不需要声明。 FileNotFoundException 是检查型的,必须在方法签名中使用 throws 捕获或声明。

创建自定义异常

可以通过扩展 Exception 或 RuntimeException 来创建自定义异常。 它们应该提供与超类构造函数匹配的构造函数。 自定义异常对于特定于应用程序的错误情况很有用。

Main.java
package com.zetcode;

class InsufficientFundsException extends Exception {
    private double amount;
    
    public InsufficientFundsException(double amount) {
        super("Insufficient funds: " + amount);
        this.amount = amount;
    }
    
    public double getAmount() {
        return amount;
    }
}

class BankAccount {
    private double balance;
    
    public BankAccount(double balance) {
        this.balance = balance;
    }
    
    public void withdraw(double amount) throws InsufficientFundsException {
        if (amount > balance) {
            throw new InsufficientFundsException(amount - balance);
        }
        balance -= amount;
    }
}

public class Main {

    public static void main(String[] args) {
        BankAccount account = new BankAccount(500);
        
        try {
            account.withdraw(600);
        } catch (InsufficientFundsException e) {
            System.out.println(e.getMessage());
            System.out.println("Missing amount: " + e.getAmount());
        }
    }
}

此示例显示了一个自定义的 InsufficientFundsException。 该异常包含有关缺失金额的附加信息。 当提款超过余额时,BankAccount 类会抛出此异常。 main 方法捕获并处理自定义异常。

多个 Catch 块

多个 catch 块可以处理不同的异常类型。 更具体的异常应该放在更一般的异常之前。 Java 7 引入了 multi-catch,用于在一个块中处理多个异常。

Main.java
package com.zetcode;

import java.util.InputMismatchException;
import java.util.Scanner;

public class Main {

    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);
        
        try {
            System.out.print("Enter numerator: ");
            int numerator = scanner.nextInt();
            
            System.out.print("Enter denominator: ");
            int denominator = scanner.nextInt();
            
            int result = numerator / denominator;
            System.out.println("Result: " + result);
            
        } catch (InputMismatchException e) {
            System.out.println("Invalid input - must be integer");
        } catch (ArithmeticException e) {
            System.out.println("Cannot divide by zero");
        } catch (Exception e) {
            System.out.println("An unexpected error occurred");
        } finally {
            scanner.close();
            System.out.println("Scanner closed in finally block");
        }
    }
}

此示例演示了多个 catch 块。 InputMismatchException 处理非整数输入。 ArithmeticException 处理除以零。 更通用的 Exception 捕获任何其他错误。 finally 块确保扫描器始终关闭,无论是否发生异常。

Try-With-Resources

Try-with-resources 会自动关闭实现 AutoCloseable 的资源。 这消除了对显式 finally 块进行资源清理的需求。 它在 Java 7 中引入,以简化资源管理。

Main.java
package com.zetcode;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Main {

    public static void main(String[] args) {

        String path = "example.txt";
        
        try (BufferedReader br = new BufferedReader(new FileReader(path))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            System.out.println("Error reading file: " + e.getMessage());
        }
    }
}

此示例使用 try-with-resources 读取文件。 BufferedReader 在 try 语句中声明,并在块退出时自动关闭。 这种方法比使用 finally 块进行手动资源管理更简洁。 如果文件读取失败,则捕获 IOException。

异常传播

异常向上调用堆栈传播,直到被捕获为止。 方法可以声明它们抛出但不处理的异常。 这允许在应用程序中的适当级别集中进行异常处理。

Main.java
package com.zetcode;

class DataProcessor {
    public void processData(String data) throws IllegalArgumentException {
        if (data == null || data.isEmpty()) {
            throw new IllegalArgumentException("Invalid data");
        }
        System.out.println("Processing: " + data);
    }
}

class DataService {
    private DataProcessor processor = new DataProcessor();
    
    public void handleData(String data) {
        try {
            processor.processData(data);
        } catch (IllegalArgumentException e) {
            System.out.println("Service error: " + e.getMessage());
            // Could log error or perform recovery here
        }
    }
}

public class Main {

    public static void main(String[] args) {
        DataService service = new DataService();
        
        service.handleData("Valid data");
        service.handleData(""); // Will trigger exception
    }
}

此示例显示了异常传播。 DataProcessor 针对无效数据抛出 IllegalArgumentException。 DataService 捕获并处理此异常。 main 方法不需要处理该异常,因为它已在服务级别处理。 这演示了分层异常处理。

来源

Java 异常类文档

在本文中,我们介绍了 Java Exception 类以及带有实际示例的异常处理。 正确的异常处理对于构建健壮且可维护的 Java 应用程序至关重要。

作者

我的名字是 Jan Bodnar,我是一位经验丰富的专业程序员。 我从 2007 年开始撰写编程文章,至今已撰写了 1,400 多篇文章和 8 本电子书。 凭借超过八年的教学经验,我致力于分享我的知识并帮助他人掌握编程概念。

列出所有Java教程