返回介绍

异常

发布于 2024-09-07 13:20:08 字数 7681 浏览 0 评论 0 收藏 0

Java 异常类层次结构图概览

Java 异常类层次结构图

Exception 和 Error 有什么区别?

在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。 Throwable 类有两个重要的子类:

  • Exception :程序本身可以处理的异常,可以通过 catch 来进行捕获。 Exception 又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。
  • ErrorError 属于程序无法处理的错误 ,我们没办法通过 catch 来进行捕获不建议通过 catch 捕获 。例如 Java 虚拟机运行错误( Virtual MachineError )、虚拟机内存不够错误( OutOfMemoryError )、类定义错误( NoClassDefFoundError )等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。

Checked Exception 和 Unchecked Exception 有什么区别?

Checked Exception 即 受检查异常 ,Java 代码在编译过程中,如果受检查异常没有被 catch 或者 throws 关键字处理的话,就没办法通过编译。

比如下面这段 IO 操作的代码:

除了 RuntimeException 及其子类以外,其他的 Exception 类及其子类都属于受检查异常 。常见的受检查异常有:IO 相关的异常、 ClassNotFoundExceptionSQLException ...。

Unchecked Exception不受检查异常 ,Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。

RuntimeException 及其子类都统称为非受检查异常,常见的有(建议记下来,日常开发中会经常用到):

  • NullPointerException (空指针错误)
  • IllegalArgumentException (参数错误比如方法入参类型错误)
  • NumberFormatException (字符串转换为数字格式错误, IllegalArgumentException 的子类)
  • ArrayIndexOutOfBoundsException (数组越界错误)
  • ClassCastException (类型转换错误)
  • ArithmeticException (算术错误)
  • SecurityException (安全错误比如权限不够)
  • UnsupportedOperationException (不支持的操作错误比如重复创建同一用户)
  • ……

Throwable 类常用方法有哪些?

  • String getMessage() : 返回异常发生时的简要描述
  • String toString() : 返回异常发生时的详细信息
  • String getLocalizedMessage() : 返回异常对象的本地化信息。使用 Throwable 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 getMessage() 返回的结果相同
  • void printStackTrace() : 在控制台上打印 Throwable 对象封装的异常信息

try-catch-finally 如何使用?

  • try 块:用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
  • catch 块:用于处理 try 捕获到的异常。
  • finally 块:无论是否捕获或处理异常, finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return 语句时, finally 语句块将在方法返回之前被执行。

代码示例:

try {
  System.out.println("Try to do something");
  throw new RuntimeException("RuntimeException");
} catch (Exception e) {
  System.out.println("Catch Exception -> " + e.getMessage());
} finally {
  System.out.println("Finally");
}

输出:

Try to do something
Catch Exception -> RuntimeException
Finally

注意:不要在 finally 语句块中使用 return! 当 try 语句和 finally 语句中都有 return 语句时,try 语句块中的 return 语句会被忽略。这是因为 try 语句中的 return 返回值会先被暂存在一个本地变量中,当执行到 finally 语句中的 return 之后,这个本地变量的值就变为了 finally 语句中的 return 返回值。

jvm 官方文档 中有明确提到:

If the try clause executes a return, the compiled code does the following:

  1. Saves the return value (if any) in a local variable.
  2. Executes a jsr to the code for the finally clause.
  3. Upon return from the finally clause, returns the value saved in the local variable.

代码示例:

public static void main(String[] args) {
  System.out.println(f(2));
}

public static int f(int value) {
  try {
    return value * value;
  } finally {
    if (value == 2) {
      return 0;
    }
  }
}

输出:

0

finally 中的代码一定会执行吗?

不一定的!在某些情况下,finally 中的代码不会被执行。

就比如说 finally 之前虚拟机被终止运行的话,finally 中的代码就不会被执行。

try {
  System.out.println("Try to do something");
  throw new RuntimeException("RuntimeException");
} catch (Exception e) {
  System.out.println("Catch Exception -> " + e.getMessage());
  // 终止当前正在运行的 Java 虚拟机
  System.exit(1);
} finally {
  System.out.println("Finally");
}

输出:

Try to do something
Catch Exception -> RuntimeException

另外,在以下 2 种特殊情况下, finally 块的代码也不会被执行:

  1. 程序所在的线程死亡。
  2. 关闭 CPU。

相关 issue: https://github.com/Snailclimb/JavaGuide/issues/190

进阶一下:从字节码角度分析 try catch finally 这个语法糖背后的实现原理。

如何使用 try-with-resources 代替 try-catch-finally

  1. 适用范围(资源的定义): 任何实现 java.lang.AutoCloseable 或者 java.io.Closeable 的对象
  2. 关闭资源和 finally 块的执行顺序:try-with-resources 语句中,任何 catch 或 finally 块在声明的资源关闭后运行

《Effective Java》中明确指出:

面对必须要关闭的资源,我们总是应该优先使用 try-with-resources 而不是 try-finally 。随之产生的代码更简短,更清晰,产生的异常对我们也更有用。 try-with-resources 语句让我们更容易编写必须要关闭的资源的代码,若采用 try-finally 则几乎做不到这点。

Java 中类似于 InputStreamOutputStreamScannerPrintWriter 等的资源都需要我们调用 close() 方法来手动关闭,一般情况下我们都是通过 try-catch-finally 语句来实现这个需求,如下:

//读取文本文件的内容
Scanner scanner = null;
try {
  scanner = new Scanner(new File("D://read.txt"));
  while (scanner.hasNext()) {
    System.out.println(scanner.nextLine());
  }
} catch (FileNotFoundException e) {
  e.printStackTrace();
} finally {
  if (scanner != null) {
    scanner.close();
  }
}

使用 Java 7 之后的 try-with-resources 语句改造上面的代码:

try (Scanner scanner = new Scanner(new File("test.txt"))) {
  while (scanner.hasNext()) {
    System.out.println(scanner.nextLine());
  }
} catch (FileNotFoundException fnfe) {
  fnfe.printStackTrace();
}

当然多个资源需要关闭的时候,使用 try-with-resources 实现起来也非常简单,如果你还是用 try-catch-finally 可能会带来很多问题。

通过使用分号分隔,可以在 try-with-resources 块中声明多个资源。

try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
   BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) {
  int b;
  while ((b = bin.read()) != -1) {
    bout.write(b);
  }
}
catch (IOException e) {
  e.printStackTrace();
}

异常使用有哪些需要注意的地方?

  • 不要把异常定义为静态变量,因为这样会导致异常栈信息错乱。每次手动抛出异常,我们都需要手动 new 一个异常对象抛出。
  • 抛出的异常信息一定要有意义。
  • 建议抛出更加具体的异常比如字符串转换为数字格式错误的时候应该抛出 NumberFormatException 而不是其父类 IllegalArgumentException
  • 使用日志打印异常之后就不要再抛出异常了(两者不要同时存在一段代码逻辑中)。
  • ……

Java 是一门面向对象的编程语言,不仅吸收了 C++语言的各种优点,还摒弃了 C++里难以理解的多继承、指针等概念,因此 Java 语言具有功能强大和简单易用两个特征。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文