当前位置: 首页 > 图灵资讯 > 技术篇> 全面掌握 Java 中的异常处理(附面试题)

全面掌握 Java 中的异常处理(附面试题)

来源:图灵教育
时间:2023-05-28 09:34:33

在程序开发中,异常处理也是我们经常使用的模块,但通常很少研究异常模块的一些知识点。比如,try-catch 要遵循的原则是什么,finally 为什么总能执行,try-catch 为什么要比较程序的执行性能和其他问题,我们会给出相应的答案,当然,有一些面试问题经常被问到的异常模块,这也是我们本文的关键内容。

介绍异常处理的基本介绍

先来看看异常处理的语法格式:

try{ ... } catch(Exception e){ ... } finally{ ... }

其中,

  • try:用于监控可能出现异常的代码段。
  • catch:是用来捕获 try 如果代码块中某些代码引起的异常 try 里面没有异常发生, catch 也不会执行。在 Java 语言中,try 后面可以有多个 catch 用于捕捉不同类型异常的代码块,需要注意的是前面的 catch 捕获异常类型不得包括以下异常类型,因此编译器会报告错误。
  • finally:不论 try-catch 如何执行,finally 它必须是最终执行的代码块,通常用于处理一些资源的释放,如关闭数据库连接、关闭开放系统资源等。

异常处理的基本使用可参考以下代码段:

try {    int i = 10 / 0;} catch (ArithmeticException e) {    System.out.println(e);} finally {    System.out.println("finally");}

多 catch 具体使用可参考以下代码段:

try {    int i = Integer.parseInt(null);} catch (ArithmeticException ae) {    System.out.println("ArithmeticException");} catch (NullPointerException ne) {    System.out.println("NullPointerException");} catch (Exception e) {    System.out.println("Exception");}

需要注意的是 Java 虚拟机会从上到下匹配错误的类型,所以前面 catch 异常类型不能包含以下异常类型。例如,如果将上述代码放在上面 Exception 将编译器放在前面会报错,具体请参考下图。

全面掌握 Java 中的异常处理(附面试题)_异常处理

发展异常处理

随着 Java 语言的发展,JDK 7 引入一些更方便的特点,更方便处理异常信息,如 try-with-resources 和 multiple catch,详见以下代码段:

try (FileReader fileReader = new FileReader("");     FileWriter fileWriter = new FileWriter("")) { // try-with-resources    System.out.println("try");} catch (IOException | NullPointerException e) { // multiple catch    System.out.println(e);}

异常处理的基本原则

先看下面的代码,有没有发现一些问题?

try {  // ...  int i = Integer.parseInt(null);} catch (Exception e) {}

上述代码看似“正常”,却违反了异常处理的两个基本原则:

  • 第一,尽量不要捕捉一般的异常,就像 Exception 这种异常应该捕获特定的异常,这对你发现问题更有帮助;
  • 第二,不要忽视异常。例如,上面的代码只添加了 catch,但没有如何处理错误,信息已经输出,所以当程序出现问题时,根本找不到问题的原因,所以记住不要直接忽视异常。
异常处理对程序性能的影响

异常处理很容易使用,但不能滥用,如以下代码片段:

// 使用 com.alibaba.fastjsonJSONArray array = new JSONArray();String jsonStr = "{'name':'laowang'}";try {    array = JSONArray.parseArray(jsonStr);} catch (Exception e) {    array.add(JSONObject.parse(jsonStr));}System.out.println(array.size());

这个代码是借助的 try-catch 由于以下两个方面,处理程序的业务逻辑通常是不可取的。

  • try-catch 代码段会产生额外的性能费用,或者从另一个角度来看,它往往会受到影响 JVM 优化代码,所以建议只捕获必要的代码段,尽量不要大 try 包裹整段代码;同时,使用异常控制代码流程也不是一个好主意,远远超过我们通常意义上的条件句(if/else、switch)要低效。
  • Java 每实例化一个 Exception,当时的栈都会发快照,这是一个比较重的操作。假如发生得很频繁,这个费用就不能忽视了。

以上使用 try-catch 可将处理业务的代码修改为以下代码:

// 使用 com.alibaba.fastjsonJSONArray array = new JSONArray();String jsonStr = "{'name':'laowang'}";if (null != jsonStr && !jsonStr.equals("")) {    String firstChar = jsonStr.substring(0, 1);    if (firstChar.equals(“{”) {        array.add(JSONObject.parse(jsonStr));    } else if (firstChar.equals([]) {        array = JSONArray.parseArray(jsonStr);    }}System.out.println(array.size());

相关面试题1. try 可以单独使用吗?

答:try 不能单独使用,否则会丢失 try 意义和价值。

2. 以下 try-catch 能正常运行吗?

try {    int i = 10 / 0;} catch {    System.out.println("last");}

答:不能正常运行,catch 以后必须包含异常信息,如 catch (Exception e)。

3. 以下 try-finally 能正常运行吗?

try {    int i = 10 / 0;} finally {    System.out.println("last");}

答:可以正常运行。

4. 以下代码 catch 异常也发生在里面。程序将如何执行?

try {    int i = 10 / 0;    System.out.println("try");} catch (Exception e) {    int j = 2 / 0;    System.out.println("catch");} finally {    System.out.println("finally");}System.out.println("main");

答:程序会打印出来 finally 抛出异常,终止运行。

5. 以下代码 finally 异常也发生在里面。程序将如何运行?

try {    System.out.println("try");} catch (Exception e) {    System.out.println("catch");} finally {    int k = 3 / 0;    System.out.println("finally");}System.out.println("main");

答:程序在输出 try 抛出异常并终止运行后,将不再执行 finally 代码异常后。

6. 常见操作有哪些异常?

答:常见操作异常如下:

  • java.lang.NullPointerException 空指针异常;原因:调用未初始化的对象或不存在的对象;
  • java.lang.ClassNotFoundException 找不到指定类别;原因:类别名称和路径加载错误,通常是程序

试图通过字符串加载特定类别引起的异常;

  • java.lang.NumberFormatException 字符串转换为数字异常;原因:非数字字符包含在字符数据中;
  • java.lang.IndexOutOfBoundsException 操作数组对象时常见数组角标越界异常;
  • java.lang.ClassCastException 数据类型转换异常;
  • java.lang.NoClassDefFoundException 未发现类定义错误;
  • java.lang.NoSuchMethodException 方法无异常;
  • java.lang.IllegalArgumentException 传递参数错误的方法。
7. Exception 和 Error 有什么区别?

答:Exception 和 Error 都属于 Throwable 的子类,在 Java 中只有 Throwable 它们可以被捕获或抛出,它们之间的区别如下:

  • Exception(异常)是程序正常运行时可以预测的事故,可以使用 try/catch 捕获处理。Exception 又分为运行异常(Runtime Exception)和异常检查(Checked Exception),异常编译可以在运行过程中通过,但如果在运行过程中出现这种未处理的异常,程序将终止运行;异常检查或使用 try/catch 捕获,或使用 throws 抛出字句声明,否则,编译将不会通过。
  • Error(错误)是指突发异常情况,通常无法恢复,如 Java 虚拟机内存溢出等问题称为 Error。
8. throw 和 throws 有什么区别?

答:它们之间的区别如下:

  • throw 语句用于方法体内,表示抛出异常由方法体内的语句处理并执行 throw 必须抛出某种异常;
  • throws 语句用在方法声明后面,该方法的调用者应处理异常,throws 代表可能有某种异常,不一定有这种异常。
9. Integer.parseInt(null) 和 Double.parseDouble(null) 抛出的异常一样吗?为什么?

答:Integer.parseInt(null) 和 Double.parseDouble(null) 抛出的异常类型不同,如下:

  • Integer.parseInt(null) 抛出的异常是 NumberFormatException;
  • Double.parseDouble(null) 抛出的异常是 NullPointerException。

至于为什么会出现不同的异常,没有特殊的原因,主要是因为这两个功能是由不同的人开发的,所以产生了两种不同的异常信息。

10. NoClassDefFoundError 和 ClassNoFoundException 有什么区别?
  • NoClassDefFoundError 是 Error(错误)类型,而 ClassNoFoundExcept 是 Exception(异常)类型;
  • ClassNoFoundExcept 是 Java 使用 Class.forName 动态加载方法,如果不加载,就会抛出 ClassNoFoundExcept 异常;
  • NoClassDefFoundError 是 Java 虚拟机或者 ClassLoader 当你试图加载类时,你找不到类订阅,也就是说,你想找到的类在编译中存在,但在操作中找不到,这次会出现 NoClassDefFoundError 的错误。
11. 使用 try-catch 为什么性能消耗更多?

答:这个问题要从 JVM(Java 在虚拟机层面找到答案。首先 Java 虚拟机在构建异常实例时需要生成异常栈轨迹。此操作将逐一访问当前线程的栈帧,并记录各种调试信息,包括栈帧指向方法的名称、类别名称、文件名称和代码中的第一行触发异常信息,这是使用异常捕获耗时的主要原因。

12. 常见的 OOM 原因是什么?

答:常见的 OOM 原因如下:

  • 未关闭数据库资源;
  • 加载特别大的图片;
  • 递归次数过多,未释放的变量一直在操作。
13. 以下程序的返回结果是什么?

public static int getNumber() {    try {        int number = 0 / 1;        return 2;    } finally {        return 3;    }}

A:0

B:2

C:3

D:1

答:3

主题分析:最终将执行程序 finally 内部代码将以前的结果覆盖为 3。

14. finally、finalize 有什么区别?

答:finally、finalize 区别如下:

  • finally 它是语句处理异常的一部分,意味着它总是执行的;
  • finalize 是 Object 一种方法是子类可以覆盖这种方法来实现资源清理,这种方法将在垃圾回收前调用。
15. 为什么 finally 总能被执行吗?

答:finally 编译器的作用总是执行的,因为编译器在编译中 Java 当代码被复制时 finally 代码块的内容,然后分别放置 try-catch 在代码块所有正常执行路径和异常执行路径的出口中, finally 无论发生什么情况,都会执行。