Java异常详解
# Java 异常详解
# Execption 和 Error 有什么区别?
execption
是程序可自行处理的异常,可以通过catch
进行捕获,可分为两类:Checked Exception
(受检查异常,必须处理)和Unchecked Exception
(不受检查异常,可以不处理)- 而
error
程序无法自行处理的错误,不建议通过catch
进行捕获,因为有些错误一般会导致JVM
直接终止线程。
# 受检查异常和不受检查异常有什么区别?
- 受检查异常(其他的
Exception
类及其子类) ,Java 代码在编译过程中,如果受检查异常没有被catch
或者throws
关键字处理的话,就没办法通过编译。(比如IO
相关的异常,SQL
异常) - 不受检查异常(
RuntimeException
及其子类) ,Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。(比如空指针异常、参数错误异常)
# Throwable 类常用方法有哪些?
String getMessage()
: 返回异常发生时的简要描述String toString()
: 返回异常发生时的详细信息String getLocalizedMessage()
: 返回异常对象的本地化信息。使用
Throwable
的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与
getMessage()
返回的结果相同。这是因为
Throwable
类中的默认实现是返回getMessage()
的结果作为本地化信息。
void printStackTrace()
: 在控制台上打印Throwable
对象封装的异常信息。
# try-catch-finally 如何使用?
try
块:用于捕获异常。其后可接零个或多个catch
块,如果没有catch
块,则必须跟一个finally
块。catch
块:用于处理 try 捕获到的异常。finally
块:无论是否捕获或处理异常,finally
块里的语句都会被执行。当在try
块或catch
块中遇到return
语句时,finally
语句块将在方法返回之前被执行。
注意:不要在 finally 语句块中使用 return!
当 try 语句和 finally 语句中都有 return 语句时,try 语句块中的 return 语句会被忽略。
这是因为 try 语句中的 return 返回值会先被暂存在一个本地变量中(从字节码中可以看出),当执行到 finally 语句中的 return 之后,这个本地变量的值就变为了 finally 语句中的 return 返回值。(简单来说,就是本地变量会被覆盖)
代码示例:
public static void main(String[] args) {
System.out.println(f(2)); // 输出 0
System.out.println("--------------------");
int i = 0;
try {
i++;
return i; // i = 1, 会先被一个本地变量存储起来,最后返回
} finally {
i++;
System.out.println(i); // 输出 2
}
}
public static int f(int value) {
try {
return value * value;
} finally {
if (value == 2) {
return 0;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
输出:
0
--------------------
2
1
2
3
4
# finally 中的代码一定会执行吗?
不一定!
在三种情况下,finally
块的代码也不会被执行:
- finally 之前虚拟机被终止运行。(
System.exit(1);
) - 程序所在的线程死亡。
- 关闭 CPU。
# 什么是 try-with-resources
try-with-resources
是 Java 7 引入的一个新特性,用于自动关闭实现了 java.lang.AutoCloseable
或者 java.io.Closeable
接口的资源。
它可以替代传统的 try-catch-finally
语句,使代码更加简洁和可读。
使用 try-with-resources
语句时,需要在 try
关键字后面的圆括号中声明资源,例如:
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
System.out.println(br.readLine());
}
2
3
在这个例子中,BufferedReader
实现了 java.lang.AutoCloseable
接口,因此可以在 try
语句中声明并自动关闭。在 try
语句执行完毕后,BufferedReader
会自动关闭,无需手动调用其 close()
方法。
如果同时声明多个资源,可以使用分号 ;
分隔,例如:
try (InputStream in = new FileInputStream("input.txt");
OutputStream out = new FileOutputStream("output.txt")) {
// 读取并写入数据
}
2
3
4
在 try
语句执行完毕后,会自动关闭 InputStream
和 OutputStream
。
- 适用范围(资源的定义): 任何实现
java.lang.AutoCloseable
或者java.io.Closeable
的对象。- 关闭资源和 finally 块的执行顺序: 在
try-with-resources
语句中,任何 catch 或 finally 块在声明的资源关闭后运行。- **作用:**使用
try-with-resources
语句可以避免一些常见的错误,如忘记关闭资源、关闭资源时发生异常等问题。它还可以使代码更加简洁和易读,不需要编写冗长的try-catch-finally
语句。
# 异常使用有哪些需要注意的地方?
不要把异常定义为静态变量,因为这样会导致异常栈信息错乱。每次手动抛出异常,我们都需要手动 new 一个异常对象抛出。
例如:
public class CustomException extends Exception { // 定义异常的静态常量 public static final CustomException INVALID_INPUT = new CustomException("Invalid input"); public static final CustomException NOT_FOUND = new CustomException("Not found"); public CustomException(String message) { super(message); } } public class Main { public static void main(String[] args) { try { throw CustomException.INVALID_INPUT; } catch (CustomException e) { System.out.println(e.getMessage()); } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17抛出的异常信息一定要有意义。
建议抛出更加具体的异常比如字符串转换为数字格式错误的时候应该抛出
NumberFormatException
而不是其父类IllegalArgumentException
。使用日志打印异常之后就不要再抛出异常了(两者不要同时存在一段代码逻辑中)。