Java异常处理,看这一篇就够了

242 阅读6分钟

看过的书,做过的项目,副业项目的灵感,一些奇技淫巧... 关注我的公众号:程序员苏桑

在计算机程序运行的过程中,总是会出现各种各样的错误,在Java编程中,异常处理是确保程序健壮性和用户体验的重要环节。本文将详细探讨在Java中何时处理异常、何时抛出异常,并对这两者进行对比分析。同时,我们还将讨论如何处理被“吃掉”的异常。

1. 什么是异常?

在Java中,异常是指在程序运行过程中发生的意外情况,通常会导致程序的正常流程被打断。Java中的异常分为两类:检查型异常(Checked Exception)和运行时异常(Runtime Exception)。

异常.png

2. 何时处理异常?

处理异常的场景

  • 可恢复的错误:例如,用户输入错误、文件未找到等情况。此时可以提示用户并要求重新输入。
  • 业务逻辑错误:例如,用户余额不足时进行提现操作。此时可以给出相应的提示信息。

示例代码:

public class Bank {
    public void withdraw(double balance, double amount) {
        try {
            if (amount > balance) {
                throw new IllegalArgumentException("余额不足");
            }
            balance -= amount;
            System.out.println("提现成功,当前余额:" + balance);
        } catch (IllegalArgumentException e) {
            System.out.println("错误: " + e.getMessage());
        }
    }
}

处理异常的原因

  • 用户体验:通过处理异常,可以给用户提供友好的提示,避免程序崩溃。
  • 程序稳定性:处理异常可以防止程序因未处理的异常而终止,保持程序的稳定性。

3. 何时抛出异常?

抛出异常的场景

  • 无法恢复的错误:例如,数据库连接失败、网络请求超时等。这些错误通常需要记录日志并终止程序。
  • 不符合预期的输入:例如,函数参数不合法时,应该抛出异常以提醒调用者。

示例代码:

public class Calculator {
    public double divide(double a, double b) {
        if (b == 0) {
            throw new ArithmeticException("除数不能为零");
        }
        return a / b;
    }
}

抛出异常的原因

  • 明确错误来源:通过抛出异常,可以清晰地指明错误发生的原因,便于后续的调试和维护。
  • 强制调用者处理:对于检查型异常,Java要求调用者必须处理这些异常,确保程序的健壮性。

4. 处理被“吃掉”的异常

在Java中,如果异常被捕获但没有被正确处理,可能会导致异常被“吃掉”。这意味着程序不会对异常做出任何反应,可能会导致后续逻辑出现问题。

示例代码:

public class ExceptionDemo {
    public void process() {
        try {
            // 可能抛出异常的代码
            int result = 10 / 0;
        } catch (ArithmeticException e) {
            // 异常被吃掉,未做任何处理
            // System.out.println("错误: " + e.getMessage()); // 被注释掉
        }
        // 后续逻辑可能会依赖于上面的结果
        System.out.println("继续执行后续逻辑");
    }
}

处理被“吃掉”的异常的方法

  • 记录日志:在捕获异常时,至少应该记录日志,以便后续分析。
  • 重新抛出异常:在捕获异常后,可以选择重新抛出异常,以便上层调用者处理。

示例代码:

public class ExceptionDemo {
    public void process() {
        try {
            int result = 10 / 0;
        } catch (ArithmeticException e) {
            // 记录日志
            System.err.println("错误: " + e.getMessage());
            // 重新抛出异常
            throw e; // 或者 throw new RuntimeException(e);
        }
        System.out.println("继续执行后续逻辑");
    }
}

5、自定义异常可以选择继承RuntimeExceptionException

1. 继承RuntimeException

  • 无检查异常(Unchecked Exception) :继承RuntimeException的异常被称为无检查异常。这意味着在编译时不需要强制捕获或声明这些异常。

  • 适用场景

    • 当异常表示程序中的逻辑错误或不当使用时,例如:非法参数、数组越界等。
    • 如果你希望调用者不必强制处理该异常,可以选择继承RuntimeException。这样可以使代码更简洁,减少不必要的异常处理。

2. 继承Exception

  • 检查异常(Checked Exception) :继承Exception的异常被称为检查异常。这意味着在编译时,调用者必须处理这些异常(通过try-catch块或在方法签名中声明)。

  • 适用场景

    • 当异常表示可以预见的错误情况,且调用者应该采取措施来处理这些情况时,例如:文件未找到、网络连接失败等。
    • 如果你希望强制调用者处理该异常,可以选择继承Exception。这有助于提高代码的健壮性,确保调用者意识到可能出现的问题。

小结

  • 选择RuntimeException:如果你的自定义异常表示程序错误或不当使用,且不希望强制调用者处理,可以选择继承RuntimeException
  • 选择Exception:如果你的自定义异常表示可预见的错误情况,且希望调用者必须处理,可以选择继承Exception

6. 异常的处理方式:就地处理 vs 向上抛出

就地处理

在某些情况下,异常可以在发生的地方进行处理。这种方式适用于可以恢复的错误,或者在当前上下文中能够有效处理的异常。

优点:

  • 局部性:异常处理逻辑与发生异常的代码在一起,便于理解。
  • 简化调用者的责任:调用者不需要处理该异常,减少了调用链的复杂性。

示例代码:

java复制代码
public class FileReader {
    public void readFile(String filePath) {
        try {
            // 读取文件的代码
            // 可能抛出IOException
        } catch (IOException e) {
            System.out.println("文件读取错误: " + e.getMessage());
        }
    }
}

向上抛出

在某些情况下,异常应该向上抛出,交给调用者处理。这种方式适用于无法在当前上下文中有效处理的异常,或者需要在更高层次进行统一处理的情况。

优点:

  • 灵活性:调用者可以根据自己的需求决定如何处理异常。
  • 集中处理:可以在更高层次进行统一的异常处理,减少代码重复。

示例代码:

java复制代码
public class FileReader {
    public void readFile(String filePath) throws IOException {
        // 读取文件的代码
        // 可能抛出IOException
    }
}

public class Application {
    public static void main(String[] args) {
        FileReader fileReader = new FileReader();
        try {
            fileReader.readFile("path/to/file.txt");
        } catch (IOException e) {
            System.out.println("文件读取错误: " + e.getMessage());
        }
    }
}

结论

在Java中,合理的异常处理是确保程序健壮性和用户体验的关键。通过及时处理可恢复的错误抛出无法恢复的错误记录被“吃掉”的异常,我们可以构建出更加健壮和用户友好的应用程序。希望本文能为你在Java异常处理方面提供一些有价值的参考。