迹忆客 专注技术分享

当前位置:主页 > 学无止境 > 编程语言 > Java >

如何在 Java 中指定和处理异常

作者:迹忆客 最近更新:2022/12/09 浏览次数:

在软件世界中,错误无时无刻不在发生。 它可能是无效的用户输入或没有响应的外部系统,或者是一个简单的编程错误。 在所有这些情况下,错误都发生在运行时,应用程序需要处理它们。 否则,它会崩溃并且无法处理进一步的请求。 Java 提供了一种强大的机制,允许我们在发生异常事件的地方或在调用堆栈中的较高层方法之一中处理异常事件。

在本文中,我们将涵盖以下主题:

  • Java异常处理常用术语
  • Java 中的已检查和未检查异常
  • 如何处理异常
  • 如何指定异常
  • 如何知道是否处理或指定异常

在深入了解 Java 异常处理的细节之前,我们需要定义一些术语。


Java 异常处理:常用术语

调用栈

调用栈是为到达特定方法而调用的方法的有序列表。 在这篇文章的上下文中,这些是被调用以获取发生错误的方法的方法。

让我们看一个例子。 Method1 调用 Method2,Method2 调用 Method3。 调用堆栈现在包含以下三个条目:

  • Method3
  • Method2
  • Method1

异常类和层次结构

异常类标识发生的错误类型。 例如,当字符串格式错误且无法转换为数字时,会抛出 NumberFormatException

与每个 Java 类一样,异常类是继承层次结构的一部分。 它必须扩展 java.lang.Exception 或其子类之一。

层次结构还用于对相似类型的错误进行分组。 IllegalArgumentException 就是一个例子。 它表示提供的方法参数无效,它是 NumberFormatException 的超类。

我们还可以通过扩展 Exception 类或其任何子类来实现自己的异常类。 以下代码片段显示了一个自定义异常的简单示例。

public class MyBusinessException extends Exception {

    private static final long serialVersionUID = 7718828512143293558L;

    public MyBusinessException() {
        super();
    }

    public MyBusinessException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }

    public MyBusinessException(String message, Throwable cause) {
        super(message, cause);
    }

    public MyBusinessException(String message) {
        super(message);
    }

    public MyBusinessException(Throwable cause) {
        super(cause);
    }
}

异常对象

异常对象是异常类的实例。 当发生中断应用程序正常流程的异常事件时,它会被创建并交给 Java 运行时。 这称为“抛出异常”,因为在 Java 中我们使用关键字 throw 将异常交给运行环境。

当方法抛出异常对象时,运行环境会在调用堆栈中搜索处理它的一段代码。 我将在本文的如何处理异常部分中详细介绍异常处理。


Java 中的已检查和未检查异常

Java 支持已检查和未检查的异常。我们可以以类似的方式使用它们,并且有很多关于何时使用哪种异常的讨论。但这超出了本文的范围。现在,让我们按照 Oracle 的 Java 教程中介绍的方法进行操作。

我们应该对所有我们可以预见的异常事件使用已检查的异常,并且编写良好的应用程序应该能够处理。检查的异常扩展了 Exception 类。抛出检查异常或调用指定检查异常的方法的方法需要指定或处理它。

未经检查的异常扩展了 RuntimeException。我们应该将它们用于无法预料的内部错误,而且大多数情况下,应用程序无法从中恢复。方法可以但不需要处理或指定未经检查的异常。抛出未检查异常的典型示例是:

  • 变量未初始化导致 NullPointerException
  • API 使用不当导致 IllegalArgumentException

如何处理异常

Java 提供了两种不同的选项来处理异常。 我们可以使用 try-catch-finally 方法来处理各种异常。 或者可以使用 try-with-resource 方法,这样可以更轻松地清理资源。

Try-Catch-Finally

这是在 Java 中处理异常的经典方法。 它可以包括 3 个步骤:

  1. 包含可能引发异常的代码部分的 try 块,
  2. 一个或多个处理异常的 catch 块,以及
  3. 在成功执行 try 块或处理抛出的异常后执行的 finally 块。

try 块是必需的,可以在有或没有 catchfinally 块的情况下使用它。

try 块

让我们先谈谈 try 块。 它包含可能引发异常的代码部分。 如果我们的代码抛出不止一个异常,可以选择是否要:

  • 为每个可能抛出异常的语句使用单独的 try 块或
  • 对可能抛出多个异常的多个语句使用一个 try 块。

以下示例显示了一个包含三个方法调用的 try 块。

public void performBusinessOperation() {
    try {
        doSomething("A message");
        doSomethingElse();
        doEvenMore();
    }
    // see following examples for catch and finally blocks
}

public void doSomething(String input) throws MyBusinessException {
    // do something useful ...
    throw new MyBusinessException("A message that describes the error.");
}    
    
public void doSomethingElse() {
    // do something else ...
}
    
public void doEvenMore() throws NumberFormatException{
    // do even more ...
}

正如我们在方法定义中看到的,只有第一个和第三个方法指定了一个异常。 第一个可能抛出 MyBusinessExceptiondoEvenMore 方法可能抛出 NumberFormatException

在下一步中,我们可以为每个要处理的异常类定义一个 catch 块和一个 finally 块。 需要指定所有未被任何 catch 块处理的已检查异常。

catch 块

我们可以在一个 catch 块中实现对一种或多种异常类型的处理。 正如我们在以下代码片段中看到的,catch 子句将异常作为参数获取。 我们可以通过参数名称在 catch 块中引用它。

public void performBusinessOperation() {
    try {
        doSomething("A message");
        doSomethingElse();
        doEvenMore();
    } catch (MyBusinessException e) {
        e.printStackTrace();
    } catch (NumberFormatException e) {
        e.printStackTrace();
    }
}

前面的代码示例显示了两个 catch 块。 一个处理 MyBusinessException,一个处理 NumberFormatException。 两个块以相同的方式处理异常。 从 Java 7 开始,你可以只用一个 catch 块来做同样的事情。

public void performBusinessOperation() {
    try {
        doSomething("A message");
        doSomethingElse();
        doEvenMore();
    } catch (MyBusinessException|NumberFormatException e) {
        e.printStackTrace();
    }
}

前面示例中的 catch 块的实现非常基础。 我只是调用 printStackTrace 方法,该方法将异常的类、消息和调用堆栈写入系统输出。

com.jiyik.example.MyBusinessException: A message that describes the error.
    at com.stackify.example.TestExceptionHandling.doSomething(TestExceptionHandling.java:84)
    at com.stackify.example.TestExceptionHandling.performBusinessOperation(TestExceptionHandling.java:25)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

在实际应用程序中,我们可能希望使用更高级的实现。 例如,我们可以向用户显示错误消息并请求不同的输入,或者可以将记录写入批处理的工作日志中。 有时,甚至可以捕获并忽略异常。

在生产中,我们还需要监控我们的应用程序及其异常处理。

finally 块

finally 块在 try 块成功执行后或 catch 块之一处理异常后执行。 因此,它是实现任何清理逻辑的好地方,例如关闭连接或 InputStream

我们可以在以下代码片段中看到此类清理操作的示例。 finally 块将被执行,即使 FileInputStream 的实例化抛出 FileNotFoundException 或文件内容的处理抛出任何其他异常。

FileInputStream inputStream = null;
try {
    File file = new File("./tmp.txt");
    inputStream = new FileInputStream(file);
    
    // use the inputStream to read a file
    
} catch (FileNotFoundException e) {
    e.printStackTrace();
} finally {
    if (inputStream != null) {
        try {
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

如你所见,finally 块提供了一个很好的选项来防止任何泄漏。 在 Java 7 之前,最好的做法是将所有清理代码放入 finally 块中。

Try-With-Resource

当 Java 7 引入 try-with-resource 语句时,情况发生了变化。 它会自动关闭所有实现 AutoCloseable 接口的资源。 大多数需要关闭的 Java 对象都是这种情况。

要使用此功能,我们唯一需要做的就是在 try 子句中实例化对象。 还需要处理或指定关闭资源时可能抛出的所有异常。

以下代码片段显示了前面的示例,其中使用了 try-with-resource 语句而不是 try-catch-finally 语句。

File file = new File("./tmp.txt");
try (FileInputStream inputStream = new FileInputStream(file);) {
    // use the inputStream to read a file
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

如大家所见,try-with-resource 语句更容易实现和阅读。 关闭 FileInputStream 时可能抛出的 IOException 的处理不需要嵌套的 try-catch 语句。 它现在由 try-with-resource 语句的 catch 块处理。


如何指定异常

如果我们不在方法内处理异常,它将在调用堆栈中传播。 如果是已检查的异常,我们还需要指定该方法可能会抛出异常。 我们可以通过在方法声明中添加一个 throws 子句来做到这一点。 因此,所有调用方法都需要自己处理或指定异常。

如果你想指出一个方法可能会抛出一个未经检查的异常,你也可以指定它。

public void doSomething(String input) throws MyBusinessException {
    // do something useful ...
    // if it fails
    throw new MyBusinessException("A message that describes the error.");
}

处理或指定异常

通常,是否应该处理或指定异常取决于用例。 正如我们可能猜到的那样,很难提供适合所有用例的建议。

一般来说,我们需要问自己以下问题:

  • 是否能够在当前方法中处理异常?
  • 你能预料到你所在类所有用户的需求吗? 处理异常会满足这些需求吗?

如果对这两个问题的回答都是肯定的,那么你应该在当前方法中处理异常。 在所有其他情况下,最好指定它。 这允许你的类的调用者实现适合当前用例的处理。


总结

好的,这就是关于 Java 异常处理的全部内容。 我将在本系列的未来文章中详细介绍最佳实践和常见错误。

如我们所见,Java 为我们提供了两种一般类型的异常:已检查异常未检查异常

我们应该对应用程序可以预期和处理的所有异常事件使用已检查的异常。 需要决定是要在方法中处理它还是指定它。 我们可以使用 try-catch-finallytry-with-resource 块来处理它。 如果决定指定异常,它就会成为方法定义的一部分,并且异常需要由所有调用方法指定或处理。

对于无法预料的内部错误,应该使用未经检查的异常。 我们不需要处理或指定此类异常,但可以按照处理或指定已检查异常的相同方式来处理或指定。

转载请发邮件至 1244347461@qq.com 进行申请,经作者同意之后,转载请以链接形式注明出处

本文地址:

相关文章

Do you understand JavaScript closures?

发布时间:2025/02/21 浏览次数:108 分类:JavaScript

The function of a closure can be inferred from its name, suggesting that it is related to the concept of scope. A closure itself is a core concept in JavaScript, and being a core concept, it is naturally also a difficult one.

Do you know about the hidden traps in variables in JavaScript?

发布时间:2025/02/21 浏览次数:178 分类:JavaScript

Whether you're just starting to learn JavaScript or have been using it for a long time, I believe you'll encounter some traps related to JavaScript variable scope. The goal is to identify these traps before you fall into them, in order to av

How much do you know about the Prototype Chain?

发布时间:2025/02/21 浏览次数:150 分类:JavaScript

The prototype chain can be considered one of the core features of JavaScript, and certainly one of its more challenging aspects. If you've learned other object-oriented programming languages, you may find it somewhat confusing when you start

JavaScript POST

发布时间:2024/03/23 浏览次数:96 分类:JavaScript

本教程讲解如何在不使用 JavaScript 表单的情况下发送 POST 数据。

扫一扫阅读全部技术教程

社交账号
  • https://www.github.com/onmpw
  • qq:1244347461

最新推荐

教程更新

热门标签

扫码一下
查看教程更方便