finally和return的执行顺序
- 如果程序是从try代码块或者catch代码块中返回时,finally中的代码总会执行。
- 当finally有返回值时,会直接返回。不会再去返回try或者catch中的返回值。
- try或catch中的return要返回的值在finally执行之前会拷贝一份。如果在finally中直接修改引用地址或者修改基本数据类型,那么返回的就是拷贝之前的。
- 如果在finally中修改要返回的某个对象中的属性,则可以看见是返回修改过后的。因为是浅拷贝,引用地址不变。
package com.hanliukui.example;
public class Main {
public static void main(String[] args) {
// write your code here
System.out.println(test1());
System.out.println(test2());
System.out.println(test3());
System.out.println(test4());
System.out.println(test5());
}
/**
* 执行try中return
* 如果程序是从try代码块或者catch代码块中返回时,finally中的代码总会执行。
*/
public static int test1(){
try {
System.out.println("try中执行了!");
return 1;
}catch (Exception e){
return -1;
}finally {
System.out.println("finally中执行了!");
}
}
/**
* 执行catch中return
* 如果程序是从try代码块或者catch代码块中返回时,finally中的代码总会执行。
*/
public static int test2(){
try {
System.out.println("try中执行了!");
int a = 8/0;
return 1;
}catch (Exception e){
return -1;
}finally {
System.out.println("finally中执行了!");
}
}
/**
* 当finally中也有return
* 当finally有返回值时,会直接返回。不会再去返回try或者catch中的返回值。
*/
public static int test3(){
try {
System.out.println("try中执行了!");
return 1;
}catch (Exception e){
return -1;
}finally {
System.out.println("finally中执行了!");
return 0;
}
}
/**
* finally中对于返回变量做的改变不会影响最终的返回结果
*/
public static int test4(){
int a=100;
try {
System.out.println("try中执行了!");
a=1;
return a;
}catch (Exception e){
return -1;
}finally {
System.out.println("finally中执行了!");
a=0;
}
}
/**
* try或catch中的return要返回的值在finally执行之前会拷贝一份。
* 如果在finally中直接修改引用地址或者修改基本数据类型,那么返回的就是拷贝之前的,如果在finally中修改要返回的某个对象中的属性,则可以看见是返回修改过后的。
*/
public static User test5(){
User user = new User();
user.setUserName("李三");
user.setAge(22);
try {
System.out.println("try中执行了!");
user.setAge(24);
System.out.println(user.hashCode());
return user;
}catch (Exception e){
e.printStackTrace();
return user;
}finally {
System.out.println("finally中执行了!");
user.setUserName("王五");
System.out.println(user.hashCode());
}
}
}
class User{
private String userName;
private Integer age;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", age=" + age +
'}';
}
}
相关开发规范
1、Java 类库中定义的可以通过预检查方式规避的 RuntimeException 异常不应该通过catch 的方式来处理,比如:NullPointerException,IndexOutOfBoundsException 等等。
原则:Java代码常见的逻辑错误,应当早暴露,早修复;严禁使用catch来隐藏这种编码错误。
说明:无法通过预检查的异常除外,比如,在解析字符串形式的数字时,可能存在数字格式错误,不得不通过 catch NumberFormatException 来实现。
正例:if (obj != null) {…}
反例:try { obj.method(); } catch (NullPointerException e) {…}
2、异常捕获后不要用来做流程控制,条件控制。
说明:异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多
3、catch 时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理。
说明:对大段代码进行 try-catch,使程序无法根据不同的异常做出正确的应激反应,也不利于定位问题,这是一种不负责任的表现。
正例:用户注册的场景中,如果用户输入非法字符,或用户名称已存在,或用户输入密码过于简单,在程序上作出分门别类的判断,并提示给用户。
4、捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。
目标1: 对于用户,能够很直观的区分异常(用户侧异常还是系统侧),而且如果是用户侧异常能够清楚地知道错误原因(如没有权限,参数不合法等);
目标2: 对于开发人员,当出现系统内部错误时,能够快速定位错误位置,以及查看错误详情;
5、事务场景中,抛出异常被 catch 后,如果需要回滚,一定要注意手动回滚事务。
6、finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。
- finally 在return 和 catch 抛出异常之前执行。
- 说明:如果 JDK7 及以上,可以使用 try-with-resources 方式。
7、不要在 finally 块中使用 return。
说明:try 块中的 return 语句执行成功后,并不马上返回,而是继续执行 finally 块中的语句,如果此处存在 return 语句,则在此直接返回,无情丢弃掉 try 块中的返回点。
8、捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。
- 如果预期对方抛的是绣球,实际接到的是铅球,就会产生意外情况。
- catch捕获对应的Exception及其子类。
9、在调用 RPC、二方包、或动态生成类的相关方法时,捕捉异常必须使用 Throwable类来进行拦截。
- 你不清楚它会抛出什么异常,通过 Throwable 你可以拦截所有。
10、防止 NPE,是程序员的基本修养。
注意 NPE 产生的场景:
1)返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE。
2) 数据库的查询结果可能为 null。
3) 集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。
4) 远程调用返回对象时,一律要求进行空指针判断,防止 NPE。
5) 对于 Session 中获取的数据,建议进行 NPE 检查,避免空指针。
6) 级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。
11、存在多个catch的时候,catch的顺序非常重要:子类必须写在前面。
说明:使用多个catch语句,每个catch分别捕获对应的Exception及其子类。JVM在捕获到异常后,会从上到下匹配catch语句,匹配到某个catch后,执行catch代码块,然后不再继续匹配。
12、捕获异常并再次抛出新的异常时,应该持有原始异常信息;
说明:如果一个方法捕获了某个异常后,又在catch子句中抛出新的异常,就相当于把抛出的异常类型“转换”了。为了能追踪到完整的异常栈,在构造异常的时候,把原始的Exception实例传进去,新的Exception就可以持有原始Exception信息
13、通常不要在finally中抛出异常。如果在finally中抛出异常,应该原始异常加入到原有异常中。说明:如果没有发生异常,就正常执行try { … }语句块,然后执行finally。如果发生了异常,就中断执行try { … }语句块,然后跳转执行匹配的catch语句块,最后执行finally。但JVM会先执行finally,然后抛出异常。