打破舒适圈,经常对 Java 知识进行总结思考,才能保证自己不掉队。
系统化重学 Java 第三篇,一起分析下 Java 异常中的常见疑问点吧。
第一问: 为什么 Java 要引入异常机制?
在没有异常处理机制的语言中,比如C语言,在函数调用过程中就只能通过检查函数返回值错误码方式来判断函数调用是否正确执行,这会扰乱正常代码流程,而且事实上高傲的程序员也很少去检查错误码。
异常处理让异常无法像错误码一样被忽略,必须写代码去捕获。另外,使用异常代替返回错误码,错误处理代码就能从主路径代码中分离出来得到简化。
参考来源链接:https://www.zhihu.com/question/19908555/answer/13320201
第二问: 为什么在 try 语句中定义的变量不能在 catch 和 finally 语句中使用?
比如以下代码是无法通过编译的:
try {
File file = new File("path");
FileInputStream fis = new FileInputStream(file);
String s = "inside";
} catch (FileNotFoundException e) {
e.printStackTrace();
System.out.println(s);
}
原因是因为,我们在 try 代码块中,并不能确认哪一行会抛出异常。
比如以上代码中,假设在第 3 行因为读取不到文件而抛出异常,那么其后的代码都不会被执行,s 根本不会被声明初始化。
所以,在 try 语句中定义的变量不能在 catch 和 finally 语句中使用,我们压根不知道这个变量本身能不能被声明。
第三问:为什么 Java 中要有受检类型异常?
首先要知道,什么是受检类型异常?
受检类型异常:它强制要求程序员为这样的异常做预备处理工作(使用 try…catch…finally 或者 throws)。在方法中要么用 try-catch 语句捕获它并处理,要么用 throws 子句声明抛出它,否则编译不会通过。
设计者考虑程序可能被运行在各种未知的环境下,程序员无法干预用户如何使用他编写的程序,所以希望用户应该为这样的异常时刻准备着。
个人理解,就好比天气可能要降温(程序可能会出异常),你妈希望你穿秋裤(受检类型异常需要被处理),你也许不冷(也许程序不会出现异常),但是必须要穿(你必须处理),否则你妈会生气(否则编译不通过)。
第四问:throw 和 throws 的区别是什么?
throw | throws |
在方法内部进行调用,后面跟的是具体的异常对象 | 在方法上进行使用,后面可以跟多个异常对象 |
用来抛出具体的异常,执行之后,后面的语句就不执行了 | 只是声明可能会抛出的异常,对应着可能会出现的问题,帮助调用者知道如何进行预处理 |
一定会抛出异常 | 是异常的一种可能性,不一定发生 |
第五问:什么时候 finally 不执行?
两种情况下, finally 出现但不执行:
- try 语句没有被执行到,如在 try 语句前就返回了,finally 语句肯定就不会执行了。这也说明了finally语句被执行的必要而非充分条件是:相应的 try 语句一定会被执行到。
- 在 try 块中有 System.exit(0); 这样的语句,它用来终止 Java 虚拟机 JVM,一旦 JVM 停止,所有的都结束了,finally 语句自然也不会被执行到。
第六问:try-catch-finally 中的 return 是怎样实现的?
我们知道 return 语句的格式如下:
return [expression];
return 语句的作用是结束方法并返回一个值,那么他返回的是哪里的值呢?
实际上,不管 expression 是一个怎样的表达式,做了什么工作,对于 return 指令来说都不重要,他只负责把操作数栈顶的值返回。
执行顺序如下:
- 执行 expression,计算该表达式,将结果保存在操作数栈顶;
- 将操作数栈顶值(expression 的结果)复制到局部变量区作为返回值;
- 执行 finally 语句块中的代码;
- 将复制到局部变量区的返回值再复制回操作数栈顶;
- 执行 return 指令,返回操作数栈顶的值;
关于操作数栈和局部变量区的说明和操作可以参考以下博客:
https://blog.csdn.net/qq_39514033/article/details/107834882
第七问:在 finally 中修改 return 的值,会不会影响最终的结果?
这个问题分三种情况,其中一种最简单的情况是:
若 finally 里有 return 语句,则一定会改变 try 或 catch 中的 return 语句,直接返回;
代码验证如下:
public static int func() {
int result = 0;
try {
result = 1;
return result;
} catch (Exception e) {
result = 2;
return result;
} finally {
result = 3;
return result;
}
}
输出结果是:3;
第二种情况:
如果 return 的数据是基本数据类型或文本字符串,则在 finally 中的改变不起作用,try 中的 return 语句依然会返回进入 finally 块之前保留的值。
代码验证如下:
public static String funcStr() {
String result = "hello";
try {
result = "1";
return result;
} catch (Exception e) {
result = "2";
return result;
} finally {
result = "3";
}
}
输出结果是:1;
第三种情况:
如果 return 的数据是引用数据类型,在 finally 中改变该引用数据类型的属性值,try 中的 return 语句返回的就是在 finally 中改变后的该属性的值。
代码验证如下:
public static Person funcPerson() {
Person result = new Person(20);
try {
result.age = 30;
return result;
} catch (Exception e) {
result.age = 40;
return result;
} finally {
result.age = 50;
}
}
static class Person {
public int age;
public Person(int age) {
this.age = age;
}
}
该函数的返回类型是 Person,age 为 50,即在 finally 中的更改是有效的。
总结:
如果没有异常出现,而且 finally 语句中没有 return,则会执行 try 里边的 return,且会将变量暂存起来(对象存的是引用的地址),再去执行 finally 中的语句。
这时候,如果返回值是基本数据类型或者字符串,则 finally 相当于更改副本,不会对暂存值有影响;
但是,如果返回值是对象,则 finally 中的语句会根据地址的副本,改变原对象的值。
所以上边最后的例子中,返回值的 age 为 50。
参考来源链接:
https://www.cnblogs.com/sunweiye/p/10952319.html
如果有任何关于 Java 异常的问题,欢迎在评论区提出,博主会及时更新解答的。