异常处理常见面试题
1. try-catch-finally 中哪个部分可以省略?
catch和finally可以省略其中一个 ,catch和finally不能同时省略 注意:格式上允许省略catch块, 但是发生异常时就不会捕获异常了,我们在开发中也一般不会这样去编写代码.
单独的try只适合处理运行时异常,try+catch适合处理运行时异常+编译时异常。如果只用try去处理编译时异常却不用catch处理,无法通过编译,必须用catch显示声明以便进一步处理。而运行时异常在编译时 没有如此规定,所以catch可以省略。 finally是不管有没捕获异常,都要进行的“扫尾”处理。
2. try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
会执行,在 return 前执行。 在 finally 中改变返回值的做法是不合理的,因为如果存在 finally 代码块,try,catch中的 return 语句不会立马返回调用者,而是记录下返回值的副本,待 finally 代码块执行完毕之后再向调用者返回其值,然后即使在 finally 中 修改了返回值,也不会返回修改后的值(仅适用于trycatch中的返回值,如果没有在try catch中返回,则finally会改变变量的值)。显然,在 finally 中返回或者修改返回值会对程序造成很大的困扰,Java中也可以通过提升编译器的语法检查级别来产生警告或错误。因为 return 表示的是 要整个方法体返回, 所以,finally 中的语句会在 return 之前执行。 但是 return 前执行的 finally 块内,对数据的修改效果对于引用类型和值类型会不同(详见下方代码块)
finally中的代码会执行详解: 执行流程:
- 先计算返回值, 并将返回值存储起来, 等待返回
- 执行finally代码块
- 将存储的返回值, 返回出去;
注意:
- 返回值是在finally运算之前就确定了(基本数据类型),并且缓存(副本)了,不管finally对该值做任何的改变,返回的值都不会改变(不在finally中return)
- finally代码中不建议包含return,因为程序会在上述的流程中提前退出,也就是说返回的值不是try或 catch中的值(下方代码块的额第二个例子)
- 如果在try或catch中停止了JVM,则finally不会执行.例如停电- -, 或通过如下代码退出 JVM:System.exit(0);
- 在 finally 语句块第一行发生了异常,除了第一行,finally 块还是会得到执行
- 在前面的代码中(包括try或catch中)用了 System.exit(int)已退出程序。 exit 是带参函数 ;但是若该语句在异常语句之后,finally 会执行
- 程序所在的线程死亡
- 关闭 CPU
public static int getInt() {
int a = 10;
try {
System.out.println(a / 0);
a = 20;
} catch (ArithmeticException e) {
a = 30;
return a; /a的副本(30)被保存了起来,
接着执行的finally只是改变a的值,并不改变保存起来的将要返回的副本(30)
*/
} finally {
a = 40;
}
return a;
}
public static int getInt1() {
int a = 10;
try {
System.out.println(a / 0);
a = 20;
} catch (ArithmeticException e) {
a = 30;
return a;
} finally {
a = 40;
//如果这样,就又重新形成了一条返回路径,由于只能通过1个return返回,所以这里直接返回40
return a;
}
}
// 修改基本类型
static int f() {
int ret = 0;
try {
return ret; // 返回 0,finally 内的修改效果不起作用
} finally {
ret++;
System.out.println("finally 执行");
}
}
// 修改引用类型
static int[] f2(){
int[] ret = new int[]{0};
try {
return ret; // 返回 [1],因为备份(副本)是数组的地址值,finally 内的修改效果起了作用
} finally {
ret[0]++;
System.out.println("finally 执行");
}
}
try-with-resources
对于文件操作 IO 流、数据库连接等开销大的资源,用完之后必须及时通过 close 方法将其关闭,否则资源会一直处于打开状态,可能会导致内存泄露等问题。 关闭资源的常用方式就是在 finally 块里是释放,即调用 close 方法。
public static void main(String[] args) {
BufferedReader br = null;
try {
String line;
br = new BufferedReader(new FileReader("d:\\hollischuang.xml"));
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
// handle exception
} finally {
try {
if (br != null) {
br.close();
}
} catch (IOException ex) {
// handle exception
}
}
}
从 Java 7 开始,jdk 提供了一种更好的方式关闭资源,使用 try-with-resources 语 句,改写一下上面的代码,效果如下:
public static void main(String... args) {
try (BufferedReader br = new BufferedReader(new FileReader("d:\\ hollischu
ang.xml"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
// handle exception
}
}
背后的原理也很简单,没有做的关闭资源的操作,编译器都做了。 所以语法糖的作用就是方便程序员的使用,但最终还是要转成编译器认识的语言(就像泛型)。
异常链
“异常链”是 Java 中流行的异常处理概念, 是指在进行⼀个异常处理时抛出了 另外⼀个异常, 由此产生了⼀个异常链条。 该技术多用于将“ 受检查异常” ( checked exception) 封装成为“非受检查 异常”( unchecked exception)( RuntimeException)。如果在处理异常时抛出⼀个新的异常,则⼀定要包含原有的异常, 这样, 处理程序才可以通过 getCause()和 initCause()⽅法来访问异常最终的根源。
try {
} catch (IOException e) {
throw new SampleException("Other IOException", e);
}
当捕获到 IOException 时,将创建一个新的 SampleException 异常, 并附加原始的异常原因,并将异常链抛出到下一个更高级别的异常处理程序。
关于代码块中有try catch的函数的返回值
如果函数没有返回值,则在try catch中都必须有返回值,否则编译失败。如果在try catch都没有返回值,则必须在finally中返回(不建议)
如果函数有返回值,则在 finally中不能有返回值(unreachable),在try或catch中可以有返回值(只能有一个,二选一)