本文已收录于:https://github.com/danmuking/all-in-one(持续更新)
前言
哈喽,大家好,我是 DanMu。今天想和大家聊聊 Java 中的异常。异常处理是一种重要的概念,因为程序总是会出现各种意料之外的问题:有可能是逻辑错误,也可能某位小可爱想看一个练习了 null 年半的练习生跳舞,程序不可能永远正确运行。但是,我们也希望程序在出错之后可以继续运行,不能影响正常用户的使用。因此,我们需要一种机制,保证程序能够在出错后进行一定程度的处理后继续运行。
Java 异常概览:
先来看看 Java 中的异常体系
Throwable
在 Java 中,所有的异常相关类都继承于一个父类,java.lang
包中的 Throwable
类。
public class Throwable implements Serializable
Throwable 中比较重要的方法有:
- String getMessage(): 返回异常发生时的简要描述
- String toString(): 返回异常发生时的详细信息
- String getLocalizedMessage(): 返回异常对象的本地化信息。使用 Throwable 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 getMessage()返回的结果相同
- void printStackTrace(): 在控制台上打印 Throwable 对象封装的异常信息
Exception 和 Error
Throwable 有两个重要的子类,Exception 和 Error:
从字面含义来说,Exception 翻译为异常,Error 翻译为错误,显然 Error 代表这更加严重的问题,在使用中也同样如此。
Error:
Error 属于程序无法处理的错误 ,不建议通过 catch 捕获 。例如 Java 虚拟机运行错误(Virtual MachineError)、虚拟机内存不够错误(OutOfMemoryError)、类定义错误(NoClassDefFoundError)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
Exception:
程序本身可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。
由于 Error 通常不建议进行处理,所以在代码编写中,我们更多接触的通常是 Exception。
Checked Exception(受检异常)和 Unchecked Exception(非受检异常)?
Checked Exception
Checked Exception
即受检查异常 ,Java 代码在编译过程中,如果受检查异常没有被 catch 或者 throws 关键字处理的话,就没办法通过编译。
比如这个简单的例子:
除了 RuntimeException 及其子类以外,其他的 Exception 类及其子类都属于受检查异常 。常见的受检查异常有:IO 相关的异常、ClassNotFoundException、SQLException…。
Unchecked Exception
Unchecked Exception
即不受检查异常 ,Java 代码在编译过程中 ,我们**即使不处理不受检查异常也可以正常通过编译。**这类异常通常与到程序运行时的状态有关。Unchecked Exception 主要包括 RuntimeException 及其子类,常见的有
- NullPointerException(空指针错误)
- IllegalArgumentException(参数错误比如方法入参类型错误)
- NumberFormatException(字符串转换为数字格式错误,IllegalArgumentException的子类)
- ArrayIndexOutOfBoundsException(数组越界错误)
- ClassCastException(类型转换错误)
- ArithmeticException(算术错误)
- ……
建议大家根据需要记上 3-4 个,面试的时候如果问到能适当的举出几个例子。
try-catch-finally 和 try-with-resources
try-catch-finally
try-catch-finally 由 3 部分组成
- try:用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
- catch:用于处理 try 捕获到的异常。
- finally:**无论是否捕获或处理异常,finally 块里的语句都会被执行。**当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。
这是一个简单的例子:
public static void main(String[] args) {try {int[] myNumbers = {1, 2, 3};System.out.println(myNumbers[10]);} catch (Exception e) {System.out.println("Something went wrong.");} finally {System.out.println("The 'try catch' is finished.");}}
注意:不要在 finally 语句块中使用 return! 当 try 语句和 finally 语句中都有 return 语句时,try 语句块中的 return 语句会被忽略。这是因为 try 语句中的 return 返回值会先被暂存在一个本地变量中,当执行到 finally 语句中的 return 之后,这个本地变量的值就变为了 finally 语句中的 return 返回值。
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 块的代码也不会被执行:
- 程序所在的线程死亡。
- 强制关闭 CPU。
虽然在实际应用中很难遇到这样子的场景,但是在面试时不少面试官还是很喜欢问这类问题,所以还是要稍微注意一下
try-with-resources
在 Java 中,所有被打开的系统资源,比如流、文件或者Socket连接等,都需要被开发者手动关闭,否则随着程序的不断运行,资源泄露将会累积成重大的生产事故。但是每一个打开的资源都伴随着一个需要被处理的异常,如果一次性需要打开多个资源,那异常的处理就会变成一场噩梦,比如这个例子:
public class Demo {public static void main(String[] args) {BufferedInputStream bin = null;BufferedOutputStream bout = null;try {bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")));int b;while ((b = bin.read()) != -1) {bout.write(b);}}catch (IOException e) {e.printStackTrace();}finally {if (bin != null) {try {bin.close();}catch (IOException e) {e.printStackTrace();}finally {if (bout != null) {try {bout.close();}catch (IOException e) {e.printStackTrace();}}}}}}
}
Fxxk!我只是想要读写两个文件,异常处理的代码竟然比业务代码长的多!这是因为,我们不仅需要关闭 BufferedInputStream ,还需要保证如果关闭 BufferedInputStream 时出现了异常, BufferedOutputStream 也要能被正确地关闭。所以我们不得不借助 finally 中嵌套 finally 大法。可以想到,打开的资源越多,finally 中嵌套的将会越深!
为了释放程序员的双手,在 1.7 中 Java 新增了一个 try-with-resources 语法糖,我们重新改写一下上面的例子
public class TryWithResource {public static void main(String[] args) {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();}}
}
只需要将要打开的资源放在try后的()
中,不同资源间用;
分割,就可以自动完成资源的关闭。代码一下子就清爽了!try-with-resources 虽好但是也有几点需要注意
- 适用范围(资源的定义): 任何实现 java.lang.AutoCloseable或者 java.io.Closeable 的对象
- 关闭资源和 finally 块的执行顺序: 在 try-with-resources 语句中,任何 catch 或 finally 块在声明的资源关闭后运行
点关注,不迷路
好了,以上就是这篇文章的全部内容了,如果你能看到这里,非常感谢你的支持!
如果你觉得这篇文章写的还不错, 求点赞👍 求关注❤️ 求分享👥 对暖男我来说真的 非常有用!!!
白嫖不好,创作不易,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!
如果本篇博客有任何错误,请批评指教,不胜感激 !
最后推荐我的IM项目DiTing(https://github.com/danmuking/DiTing-Go),致力于成为一个初学者友好、易于上手的 IM 解决方案,希望能给你的学习、面试带来一点帮助,如果人才你喜欢,给个Star⭐叭!
参考资料
Java基础常见面试题总结(下)