招商银行网络面试二面的面试题,了解 Exception 和 Error 之间的差异
在 Java 编程语言中,所有异常的根类是 java.lang
包下的 Throwable
类。它的两个主要子类分别是:
Exception
:程序可以处理的异常,这些异常可以通过catch
语句来捕获。Exception
类进一步分为 Checked Exception(受检查异常,必须处理)和 Unchecked Exception(不受检查异常,可以选择不处理)。Error
:此类包含程序无法处理的错误,建议不要通过catch
语句来捕获这些错误。例如 Java 虚拟机运行错误(VirtualMachineError
)、内存不足错误(OutOfMemoryError
)、类定义错误(NoClassDefFoundError
)等。发生这些错误时,一般情况下,Java 虚拟机(JVM)会选择终止线程。
Checked Exception 和 Unchecked Exception 的区别
Checked Exception:受检查异常。在 Java 编译过程中,如果未通过 catch
或 throws
关键字处理受检查异常,代码无法通过编译。
例如,以下 IO 操作的代码:
除了 RuntimeException
及其子类以外,其他的 Exception
类及其子类都属于受检查异常。常见的受检查异常包括:与 IO 相关的异常、ClassNotFoundException
、SQLException
等。
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
语句会被忽略。
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());
System.exit(1); // 终止当前 JVM
} finally {
System.out.println("Finally");
}
输出结果:
Try to do something
Catch Exception -> RuntimeException
此外,有以下两种特殊情况下,finally
块的代码也不会被执行:
- 程序所在的线程死亡。
- CPU 被关闭。
如何使用 try-with-resources 代替 try-catch-finally?
- 适用范围(资源的定义): 任何实现
java.lang.AutoCloseable
或java.io.Closeable
接口的对象。 - 关闭资源和 finally 块的执行顺序: 在
try-with-resources
语句中,任何catch
或finally
块在声明的资源关闭后运行。
《Effective Java》中指出:
面对必须关闭的资源时,总是应该优先使用
try-with-resources
而不是try-finally
,这样生成的代码更简洁、更清晰,异常处理也更有效。
在 Java 中,类似 InputStream
、OutputStream
、Scanner
、PrintWriter
等资源都需要调用 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
可能会引发许多问题。
异常使用的注意事项
- 不要将异常定义为静态变量,因为这会导致异常栈信息错乱。每次手动抛出异常时,必须手动创建一个新的异常对象。
- 抛出的异常信息必须具备意义。
- 建议抛出更具体的异常,例如在字符串转换为数字格式错误时,应抛出
NumberFormatException
而不是其父类IllegalArgumentException
。 - 使用日志打印异常后,避免再次抛出异常(两者不应同时出现在同一逻辑中)。