跳转到内容

抛出和捕获异常

75% developed
来自 Wikibooks,为开放世界提供开放书籍

导航 异常 主题: v  d  e )


语言编译器擅长指出程序中大部分错误代码,但是有一些错误只有在程序执行时才会显现。考虑 代码清单 6.1;这里,程序定义了一个方法 divide,它执行一个简单的除法运算,以两个整数作为参数参数并返回它们的除法结果。可以安全地假设,当调用 divide(4, 2) 语句时,它将返回数字 2。但是,考虑下一条语句,该语句依赖于提供的命令行参数来生成除法运算。如果用户提供数字零(0)作为第二个参数怎么办?我们都知道零除是不可能的,但编译器不可能预料到用户会提供零作为参数。

Computer code 代码清单 6.1: SimpleDivisionOperation.java
public class SimpleDivisionOperation {
  public static void main(String[] args) {
    System.out.println(divide(4, 2));
    if (args.length > 1) {
      int arg0 = Integer.parseInt(args[0]);
      int arg1 = Integer.parseInt(args[1]);
      System.out.println(divide(arg0, arg1));
    }
  }

  public static int divide(int a, int b) {
    return a / b;
  }
}
Standard input or output 代码清单 6.1 的输出
$ java SimpleDivisionOperation 1 0
2
Exception in thread "main" java.lang.ArithmeticException: / by zero
     at SimpleDivisionOperation.divide(SimpleDivisionOperation.java:12)
     at SimpleDivisionOperation.main(SimpleDivisionOperation.java:7)

这种在程序运行时导致错误解释的异常代码通常会导致在 Java 中称为异常的错误。当 Java 解释器遇到异常代码时,它会停止执行并显示有关发生的错误的信息。此信息称为堆栈跟踪。上面的例子中的 堆栈跟踪 告诉我们更多关于错误的信息,例如发生异常的线程——"main"——异常类型——java.lang.ArithmeticException,一个易于理解的显示消息——/ by zero,以及可能发生异常的确切方法和行号。

异常对象

[edit | edit source]

前面的异常可以由开发人员显式创建,就像以下代码一样

Computer code 代码清单 6.2: SimpleDivisionOperation.java
public class SimpleDivisionOperation {
  public static void main(String[] args) {
    System.out.println(divide(4, 2));
    if (args.length > 1) {
      // Convert a string to an integer
      int arg0 = Integer.parseInt(args[0]);
      int arg1 = Integer.parseInt(args[1]);
      System.out.println(divide(arg0, arg1));
    }
  }

  public static int divide(int a, int b) {
    if (b == 0) {
      throw new ArithmeticException("You can\'t divide by zero!");       
    } else {
      return a / b;
    }
  }
}
Standard input or output 代码清单 6.2 的输出
$ java SimpleDivisionOperation 1 0
2
Exception in thread "main" java.lang.ArithmeticException: You can't divide by zero!
at SimpleDivisionOperation.divide(SimpleDivisionOperation.java:14)
at SimpleDivisionOperation.main(SimpleDivisionOperation.java:8)

注意,当b 等于零时,没有返回值。它不是由 Java 解释器本身生成的 java.lang.ArithmeticException,而是由编码器创建的异常。结果是一样的。它向您展示了一个异常是一个对象。它的主要特点是可以抛出。异常对象必须继承自 java.lang.Exception。标准异常有两个构造函数

  1. 默认构造函数;以及,
  2. 一个带字符串参数的构造函数,这样你就可以在异常中放置相关信息。
Example 代码部分 6.1: 使用默认构造函数的异常对象的实例。
new Exception();
Example 代码部分 6.2: 通过在构造函数中传递字符串来实例化 Exception 对象。
new Exception("Something unexpected happened");

这个字符串可以稍后使用各种方法提取,如 代码清单 6.2 所示。

你可以使用关键字 throw 抛出任何类型的 Throwable 对象。它会中断方法。throw 语句之后的任何内容都不会被执行,除非抛出的异常被 处理。异常对象不会从方法中返回,而是从方法中抛出。这意味着异常对象不是方法的返回值,调用方法也可以被中断,依此类推……

通常,您会为每种不同类型的错误抛出不同类型的异常。有关错误的信息既表示在异常对象内部,也隐含在异常类名称中,因此在更大的上下文中的人可以弄清楚如何处理您的异常。通常,唯一的信息是异常类型,并且没有有意义的信息存储在异常对象中。

Oracle 标准异常类

[edit | edit source]

下面的框 6.1 介绍了 java.lang 包中的各种异常类。

框 6.1: Java 异常类

Throwable
Throwable 类是 Java 语言中所有错误和异常的超类。只有此类(或其子类之一)的实例对象才会被 Java 虚拟机抛出或可以被 Java throw 语句抛出。
Throwable 包含其线程在创建时执行堆栈的快照。它还可以包含一个消息字符串,提供有关错误的更多信息。最后,它可以包含一个原因:另一个导致此 Throwable 被抛出的 Throwable。原因工具是在 1.4 版本中添加的。它也被称为链式异常工具,因为原因本身可以有一个原因,依此类推,导致一系列异常,每个异常都由另一个异常引起。
Error
Error 指示严重问题,合理的应用程序不应该尝试处理这些问题。大多数此类错误都是异常情况。
Exception
Exception 类及其子类是 Throwable 的一种形式,它指示合理的应用程序可能希望处理的条件。这也是程序员在添加业务逻辑异常时可能要扩展的类。
RuntimeException
RuntimeException 是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。方法不需要在可能在方法执行期间抛出但未捕获的 RuntimeException 的任何子类中声明其 throws 子句。
图 6.2: JCL 中的异常类及其继承模型。

try/catch 语句

[edit | edit source]

Try/Catch 绝对更好。默认情况下,当抛出异常时,当前方法会中断,调用方法也会中断,依此类推,直到main 方法。抛出的异常也可以使用try/catch 语句捕获。下面是如何使用try/catch 语句:

Example 代码部分 6.3: 将除法运算放入try 块中。
int a = 4;
int b = 2;
int result = 0;
try {
  int c = a / b;
  result = c;
} catch(ArithmeticException ex) {
  result = 0;
}
return result;

已执行的代码行已突出显示。当没有抛出异常时,方法流程执行try语句,而不是catch语句。

Example 代码部分 6.4:捕获“除以零”错误。
int a = 4;
int b = 0;
int result = 0;
try {
  int c = a / b;
  result = c;
} catch(ArithmeticException ex) {
  result = 0;
}
return result;

由于在第 5 行抛出了异常,第 6 行未执行,但异常被catch语句捕获,因此执行catch块。以下代码也被执行。请注意,catch语句将异常作为参数。还有第三种情况:当异常不属于与参数相同的类时

Example 代码部分 6.5:未捕获异常。
int a = 4;
int b = 0;
int result = 0;
try {
  int c = a / b;
  result = c;
} catch(NullPointerException ex) {
  result = 0;
}
return result;

就好像没有try/catch语句一样。异常被抛出到调用方法。

try/catch语句可以包含多个catch块,以不同的方式处理不同的异常。每个catch块必须采用一个不同可抛出类的参数。抛出的对象可能匹配多个catch块,但只有第一个与对象匹配的catch块将被执行。当且仅当以下条件满足时,catch 块将捕获抛出的异常

  • 抛出的异常对象与 catch 块指定的异常对象相同。
  • 抛出的异常对象是 catch 块指定的异常对象的子类型。

这意味着catch块的顺序很重要。因此,不能将捕获所有异常的catch块(以java.lang.Exception作为参数)放在捕获更具体异常的catch块之前,因为第二个块永远不会被执行。

Example 代码部分 6.6:使用 catch 块进行异常处理。
try {
  // Suppose the code here throws any exceptions,
  // then each is handled in a separate catch block.

  int[] tooSmallArray = new int[2];
  int outOfBoundsIndex = 10000;
  tooSmallArray[outOfBoundsIndex] = 1;

  System.out.println("No exception thrown.");
} catch(NullPointerException ex) {
  System.out.println("Exception handling code for the NullPointerException.");
} catch(NumberFormatException ex) {
  System.out.println("Exception handling code for the NumberFormatException.");
} catch(ArithmeticException | IndexOutOfBoundsException ex) {
  System.out.println("Exception handling code for ArithmeticException"
    + " or IndexOutOfBoundsException.");
} catch(Exception ex) {
  System.out.println("Exception handling code for any other Exception.");
}
Standard input or output 代码部分 6.6 的输出
Exception handling code for ArithmeticException or IndexOutOfBoundsException.

在第 14 行,我们使用了一个 **多捕获** 子句。它从 JDK 7 开始可用。这是多个 **catch** 子句的组合,它允许你在单个处理程序中处理异常,同时保留它们的类型。因此,它们不会被封装在一个父异常超类中,而是保留它们各自的类型。

你也可以在这里使用java.lang.Throwable类,因为 **Throwable** 是 *应用程序特定* **Exception** 类的父类。但是,在 Java 编程圈中不建议这样做。这是因为 **Throwable** 恰好也是 *非应用程序特定* **Error** 类的父类,而这些类不打算被显式处理,因为它们由 JVM 本身处理。

finally

[编辑 | 编辑源代码]

可以在catch块之后添加一个finally块。finally块始终被执行,即使没有抛出异常、异常被抛出并被捕获,或者异常被抛出但未被捕获。它是一个放置应始终在不安全操作(如文件关闭或数据库断开连接)之后执行的代码的地方。你可以定义一个没有catch块的try块,但是在这种情况下,它必须后跟一个finally块。

异常处理示例

[编辑 | 编辑源代码]

让我们检查以下代码

Warning 代码部分 6.7:处理异常。
public void methodA() throws SomeException {
    // Method body
}

public void methodB() throws CustomException, AnotherException {
    // Method body
}

public void methodC() {
    methodB();
    methodA();
}

代码部分 6.7 中,methodC 是无效的。因为methodAmethodB 传递(或抛出)异常,所以methodC 必须准备好处理它们。这可以通过两种方式处理:一个try-catch 块,它将在方法内部处理异常,以及一个throws 子句,它将反过来将异常抛出给调用者以进行处理。上面的示例将导致编译错误,因为 Java 对异常处理非常严格。因此,程序员被迫在某个时刻处理任何可能的错误情况。

方法可以对异常执行两件事:通过throws 声明要求调用方法处理它,或者通过try-catch 块在方法内部处理异常。

要正常工作,原始代码可以以多种方式修改。例如,以下

Example 代码部分 6.8:捕获和抛出异常。
public void methodC() throws CustomException, SomeException {
  try {
    methodB();
  } catch(AnotherException e) {
    // Handle caught exceptions.
  }
  methodA();
}

来自methodBAnotherException 将在本地处理,而CustomExceptionSomeException 将被抛出给调用者以进行处理。当开发人员必须在这两个选项之间做出选择时,大多数开发人员会感到尴尬。这种类型的决定不应在开发时做出。如果你是开发团队,应该在所有开发人员之间讨论,以制定共同的异常处理策略。

关键字参考

[编辑 | 编辑源代码]


Clipboard

待办事项
添加一些与 变量 中的练习类似的练习


华夏公益教科书