IntelliJ IDEA Java

简单攻略:如何抛出 Java 异常

Read this post in other languages:

Java 中的异常用于指示程序执行期间发生并扰乱正常指令流的事件。 发生异常时,Java 运行时会自动停止当前方法的执行, 将带有错误信息的异常对象传递到可以处理异常的最近的 catch 块。

虽然妥善捕获和处理异常很重要,但了解如何有效抛出异常也同样重要。 在这篇博文中,我们将探讨引发 Java 异常的细节,涵盖不同类型的异常、如何创建自定义异常等。

如何抛出异常

要让 Java 运行时知道代码中发生了异常,首先必须抛出一个异常。 在 Java 中,您可以使用 throw 关键字调用 Java 虚拟机 (JVM) 中的异常机制:

throw new Exception("Something went wrong!");

抛出异常时,您是在创建新的异常对象。 这个对象包含发生的事件的信息。 这些信息通过异常类型和多个其他属性反映,例如异常消息,它可以更详细地描述发生的情况。 运行时还将以堆栈跟踪的形式,通过异常抛出位置的信息丰富异常对象。

为了说明,参考以下程序:

public void main() throws Exception {
    runFirstMethod();
}

public void runFirstMethod() throws Exception {
    runSecondMethod();
}

public void runSecondMethod() throws Exception {
    throw new Exception("Something went wrong!");
}

main 方法调用 runFirstMethod, 后者又调用 runSecondMethod。 新的异常抛出,消息为 "Something went wrong!"。 运行此程序时,您可以看到输出中打印的消息,异常发生位置的堆栈跟踪将打印到控制台:

抛出未处理的异常时,应用程序将停止执行并返回非零退出代码。 要处理异常,您可以在调用堆栈中的任何位置使用 try/catch 块将其包围。 例如,您可以在 main() 方法或 runFirstMethod() 中“捕获”异常并将错误记录到控制台:

public void main() throws Exception {
    try {
        runFirstMethod();
    } catch (Exception ex) { // can catch here...
        System.out.println("An error occurred: " + ex.getMessage());
    }
}

public void runFirstMethod() throws Exception {
    try {
        runSecondMethod();
    } catch (Exception ex) { // ...or here
        System.out.println("An error occurred: " + ex.getMessage());
    }
}

public void runSecondMethod() throws Exception {
    throw new Exception("Something went wrong!");
}

我们将在以后的文章中更详细地研究异常捕获。 现在,您只需记住异常可以被捕获并妥善处理。

哪些类可以用来表示异常?在 Java 中,只有继承 java.lang.Throwable 的类可被抛出。 如果查看 Throwable 的类层次结构,您可以发现两个子类型:java.lang.Errorjava.lang.Exception

Error 类由 JVM 使用,代表应用程序通常不应尝试捕获的严重问题,例如当 JVM 无法分配对象时可能会发生 OutOfMemoryError。这种情况已经无法恢复了!

Exception 类是可以通过 try/catch 块恢复的异常的典型上级。 例如,如果由于找不到文件而引发 IOException,代码可以向应用程序的用户显示一条消息或要求再次指定文件路径。 不需要这样的异常来结束应用程序的执行。

Java 平台中的大多数异常都是 Exception 类的子类,指示可能发生的各种类型的异常。 IOException 表示 I/O 操作中断或失败。 FileNotFoundExceptionIOException 的子类,当特定路径名的文件不存在时抛出。 应用程序中的自定义异常类型通常也会继承自 Exception 类。

某些异常具有不同的超类,继承自 RuntimeExceptionRuntimeException 的实现通常用于表示 API 使用不正确。 IndexOutOfBoundsException 异常类型可能会在您尝试访问超出范围的数组或字符串中的索引时被抛出。 另一个示例是 NullPointerException,访问未指向任何对象(并且不引用任何内容,或 null)的变量时可能会被抛出。

受检与非受检异常

在 Java 中,有两类异常:受检异常和非受检异常。 这些与具体异常使用了什么超类密切相关。

非受检异常是在编译时未检查的异常。 继承 ErrorRuntimeException 类的异常是非受检异常,即使编译器可以确定此类异常可能发生,代码仍然会编译。

受检异常是在编译时检查的异常。 如果方法包含引发 ThrowableExceptionException 继承者类型异常的代码,则该方法必须自行处理异常,或者发出信号表明异常必须由调用方法处理。 如果未处理异常,代码将无法编译。

我们再看一下示例代码:所有方法签名都有后缀 throws Exception

public void main() throws Exception {
    runFirstMethod();
}

public void runFirstMethod() throws Exception {
    runSecondMethod();
}

public void runSecondMethod() throws Exception {
    throw new Exception("Something went wrong!");
}

throws Exception 语法通知 Java 编译器任何调用代码都必须处理 Exception 类型的异常。 在这个示例中,应用程序的 main() 方法不处理异常,这意味着应用程序不处理此调用堆栈中的潜在异常,并且可能会出错。

如果要抛出 RuntimeException,编译器不会要求您使用 throws 关键字列出该异常。 异常将为非受检。

移除 throws Exception 后缀之一时,您可以看到受检异常在编辑器中可视化。 IntelliJ IDEA 几乎将立即显示一条错误消息,指出存在“未处理的异常”,并且代码将无法编译。

当您调出上下文菜单时(在 macOS 上按 ⌥⏎ 或在 Windows/Linux 上按 Alt+Enter),IDE 将建议将异常类型添加到方法签名或将对 runSecondMethod() 的调用包装在 try/catch 块中 。

向方法签名添加异常

注意,java.nio 等第三方代码中可能发生的异常也是如此。 在下方屏幕截图中,readString 方法可能会抛出 IOException,IntelliJ IDEA 再次建议向方法签名 (throws IOException) 添加异常或使用 try/catch 块处理异常。

受检异常和 throws 语法是注解代码编程接口的好方法。 通过提供有关潜在异常的信息,任何调用者都可以决定如何处理异常:向方法签名添加 throws 将其忽略,或者通过 catch 捕获和处理异常。

创建自定义 Java 异常类

Java 平台附带大量异常类,可用于在代码中发生错误时发出信号。 但是,如果您需要尚不可用的特定异常类型,则可能需要自行实现。

您可以编写继承自 java.lang.Exception 的自定义异常类以供编译器检查,或者,如果您希望不检查自定义异常,则可以选择 java.lang.RuntimeException。 但是,在大多数情况下,自定义异常类将被检查并继承 java.lang.Exception。 异常类名以“Exception”结尾也很常见。

让我们创建一个自定义异常,用来表示银行透支。例如,当用户尝试从账户中提取的资金多于可用余额时,此异常会被抛出。 OverdraftException 最简单的形式可能如下所示:

public class OverdraftException extends Exception {
    public OverdraftException(String message) {
        super(message);
    }
}

抛出 OverdraftException 相当简单:异常只需要一个 message 来描述出了什么问题:

throw new OverdraftException("Attempt to withdraw $100 with balance of $50.");

实现自定义异常类时,您可能需要为调用代码提供一些附加信息。 例如,OverdraftException 可以通过对应属性使可用余额和尝试提款金额可访问:

public class OverdraftException extends Exception {
    private final BigDecimal amount;
    private final BigDecimal balance;

    public OverdraftException(BigDecimal amount, BigDecimal balance, String message) {
        super(message);
        this.amount = amount;
        this.balance = balance;
    }

    public BigDecimal getAmount() {
        return withdrawalAmount;
    }

    public BigDecimal getBalance() {
        return balance;
    }
}

抛出异常时,必须将金额和余额传递到 OverdraftException 构造函数中。 调用代码现在可以使用异常消息,或者在处理异常时使用额外信息,例如提示用户降低提款金额:

try {
    throw new OverdraftException(
            BigDecimal.valueOf(100),
            BigDecimal.valueOf(5),
            "Attempt to withdraw $100 with balance of $50.");
} catch (OverdraftException ex) {
    System.out.println(ex.getMessage());
    System.out.println("Would you like to withdraw another amount?");
    System.out.println("You can withdraw up to $" + ex.accountBalance);
    
    // ...
}

结论

在这篇文章中,我们看到了 Java 中的异常用于指示代码中的错误。 发生异常时,Java 运行时会停止当前方法的执行,并将异常对象传递到可以处理它的最近的 catch 块。 要抛出异常,您可以使用 throw 关键字,后跟异常对象。

Java 中有两种类型的异常:受检异常和非受检异常。 受检异常在编译时检查,并且必须被处理或声明抛出。 非受检异常在编译时不会被检查。

自定义异常类可以通过继承 Exception 类创建。 这些自定义异常可以提供有关错误的附加信息。 通过传达异常信息,代码编程接口变得更加清晰,调用者可以决定如何处理异常。

现在就用 IntelliJ IDEA 尝试一下吧!

本博文英文原作者:

image description

Discover more