【Java】Java关键字 - finally关键字

Posted by 西维蜀黍 on 2019-01-18, Last Modified on 2021-09-21

[例子1] final的意义

public static int test1(){
        int i = 0;
        try{
            System.out.println("try"); //1
            return i;
        }finally {
            System.out.println("finally"); //2
        }
    }

执行结果

try
finally

分析

无论try语句或catch语句中是否有return语句,finally语句都一定会被执行。

总结

总结来说,我们可以把必须要执行的操作(比如资源释放操作:关闭流、关闭数据库连接,释放锁)放到finally语句里,以确保其一定会被执行。

  • 在某些情况下,try语句压根就没有执行到,那么finally语句也一定就不会执行到了。
  • 还有一种情况,就是在try块中有System.exit(0);这样的语句,System.exit(0);会终止Java虚拟机(JVM)。因此,连JVM都停止了,当然finally语句也不会被执行到。

[例子2]

我们再来看一个程序:

public class Main {
    public static void main(String[] args) {
        System.out.println(test4());
    }

    public static int test4() {
        int b = 20;
        try {
            System.out.println("try block");
            b = b / 0;

            return 1;
        } catch (Exception e) {
            b += 15;
            System.out.println("catch block");
        } finally {
            System.out.println("finally block");
            if (b > 25) {
                System.out.println("b>25, b = " + b);
            }
            b += 50;
        }
        return 204;
    }
}

结果

try block
catch block
finally block
b>25, b = 35
204

说明

打印204说明,当在执行try语句块的过程中抛出异常,则会直接进入catch语句(前提当然是存在catch语句),而对于在try 语句块中的抛出异常位置后的代码,不会被执行。

[例子3]

再来看一个程序:

public class FinallyTest1 {
    public static void main(String[] args) {
        System.out.println(test11());
    }    
    public static String test11() {
        try {
            System.out.println("try block");
           return test12();
      } finally {
           System.out.println("finally block");
       }
  }

  public static String test12() {
       System.out.println("test12");
       return "test12 return";
   }    
}

结果

try block
test12
finally block
test12 return

说明

return statement先于finally block被打印,这说明:

  • System.out.println("try block");先被执行
  • 开始执行return test12();中的test12()
    • 执行System.out.println("return statement");
    • 执行test12()return "test12 return",即函数test11的返回值被赋值为test12 return
  • 执行finally语句中的System.out.println("finally block");
  • test11()函数返回,返回值为test12 return
  • 打印test11()函数的返回值,即打印test12 return

结论

try语句块或catch语句块中的return语句会先被执行(但不会立即返回),之后再去执行finally语句块。

而当等到finally语句块执行完成后,函数返回才会执行。

[例子4] - 值类型

再来看一个程序:

public class Main {
    public static void main(String[] args) {
        int j = query();
        System.out.println(j);
    }
    public static int query() {
        int i = 0;
        try {
            System.out.print("try\n");
            return i += 40;
        } catch (Exception e) {
            System.out.print("catch\n");
            i += 20;
        } finally {
            System.out.print("finally1  i:"+i + "\n");
            i += 10;
            System.out.print("finally2  i:"+i + "\n");
            System.out.print("finally3\n");
            //return i;
        }
        System.out.print("finish");
        return 200;
    }
}

执行结果

try
finally1  i:40
finally2  i:50
finally3
40

分析

  1. 输出finally1 i:40,说明在执行final语句块之前,i += 40会被执行。
  2. 输出finally1 i:40,同时说明即使try语句块中有return语句块,fianlly还是会被执行。这就是所谓的”finally一定会执行“。
  3. 输出finally2 i:50 finally3 40,说明当执行try语句中的return i += 40;时,JVM会先执行i += 40(等价于i = i + 40),因此此时i的值为40,并且将变量i复制一份(暂且称为i_return)。此后,JVM继续去执行finally语句(注意,finally语句中没有return语句)。
  4. finish没有被输出,说明只要在try语句块和其对应的finally语句块中包含return语句,则在整个try结构后的代码永远不会被执行。

结论

  • finally语句是在return语句执行后,return语句返回之前执行的。
    • 从JVM管理内存的角度来分析,return返回后,就代表着方法执行结束,相应的该方法的栈帧就出栈了。而这个时候也就意味着,return返回必须在最后执行(否则会出现该方法对应的栈帧已经出栈,但是该方法(的finally语句块)任然在执行),所以finally语句是在return返回之前执行的!
  • 当在try语句块或catch语句块中包含return语句(而finally语句块中不包含return语句),且return语句中包含赋值操作(或修改值操作),且赋值或修改的变量是值类型时:
    1. return语句中的赋值先被执行
    2. 赋值后的新变量会被复制一份
    3. 执行finally语句块。因此,在fianlly语句块中修改 try语句块或catch语句块中return语句中的变量不会影响return的结果

[例子5] - 值类型

再来看一个程序:

public class Main {
    public static void main(String[] args) {
        int j = query();
        System.out.println(j);
    }
    public static int query() {
        int i = 0;
        try {
            System.out.print("try\n");
            return i += 40;
        } catch (Exception e) {
            System.out.print("catch\n");
            i += 20;
        } finally {
            System.out.print("finally1  i:"+i + "\n");
            i += 10;
            System.out.print("finally2  i:"+i + "\n");
            System.out.print("finally3\n");
            return i;
        }
        System.out.print("finish");
        return 200;
    }
}

执行结果

try
finally1  i:40
finally2  i:50
finally3
50

分析

注意!这个例子上上面一个例子唯一的区别在于,在finally语句块中存在一个return语句。

输出50,说明:

  1. 当执行try语句块中的return i += 40;时,JVM会先执行i += 40(等价于i = i + 40),因此执行后i的值为40,并且将变量i复制一份(暂且称为i_return1)。此后,JVM继续去执行finally语句块。
  2. 执行finally语句块中的i += 10;(此后,i的值为50)。
  3. 执行finally语句块中的return i;,此时,会将变量i复制一份(称为i_return2),并且将变量i_return2的值作为函数的返回值返回。

总结

当在try语句块或catch语句块中包含return语句,且finally语句块中也包含return语句),且return语句中包含赋值操作(或修改值操作),且赋值或修改的变量是值类型时:

  1. try语句块中的return语句中的赋值先被执行
  2. 赋值后的新变量会被复制一份
  3. 执行finally语句块,
    1. finally语句块中的return语句被执行
    2. 将finally语句块中的return语句中的值作为函数的返回值(而try语句块中的return语句相当于被覆盖了)

[例子6] - 引用类型

如果这个返回值是引用类型,情况又会有所不同。

import java.util.ArrayList;
import java.util.List;
public class Main {
   public static void main(String[] args) {
       List<String> cats = new ArrayList<>();
       cats  = query(cats);
       System.out.println("----");
       for(String cat : cats)
           System.out.println(cat);
   }
   public static List<String> query(List<String> cats) {
       int i = 0;
       try {
           System.out.print("try\n");
           cats.add("xiaoMeng");
           return cats;
       } catch (Exception e) {
           System.out.print("catch\n");
       } finally {
           System.out.print("finally\n");
           cats.add("qiaoGeLi");
       }
       System.out.println("finish");
       return null;
   }
}

执行结果

try
finally
----
xiaoMeng
qiaoGeLi

分析

由于cats是List类型,即一个引用类型。对于引用类型的复制只能在当前进程栈帧的局部变量表中复制一份,而其指向到位于堆(Heap)中的内存地址并没有改变(本质上,仍然是同一个对象)。

结论

当在try语句块或catch语句块中包含return语句,且finally语句块中也包含return语句),且return语句中包含赋值操作(或修改值操作),且赋值或修改的变量是引用类型时,无论finally语句块中有没有return语句,若在finally语句块中修改这个引用对象,都会影响最终函数返回值的结果。

总结

  • finally语句总是在retrun语句执行后,return返回之前执行的,也就是说finally必执行(当然是建立在try执行的基础上)
  • 当在try语句或catch语句中有return语句时,finally语句中修改了返回值的值。若修改的返回值为基本数据类型(比如int,double之类),finally语句中没有return语句,则finally语句中对返回值的修改不会影响最终的返回结果
  • 当在try语句或catch语句中有return语句时,finally语句中修改了返回值的值。若改返回值为引用类型(比如对象,list,map之类),无论finally语句中有没有return语句,finally语句中对返回值的修改都会影响最终的返回结果

Reference