吃透面试三:深入理解 final 、finally 和 finalize

一,final

final青铜篇

final可以修饰类,方法,变量。

  • 当修饰类时表示此类不能被继承。
  • 修饰方法时表示此方法不能被重写。
  • 修饰变量时必须要初始化,可以在定义时,代码快,构造函数中初始化,一旦初始化不允许被修改。

final王者篇

1,匿名内部类访问局部变量为什么要强制加上final

我们看如下代码:

public class Test {
    
    
    public static void main(String[] args) {
    
    
        int a = 8;
        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println(a);
            }
        }).start();
    }
}

在jdk1.8之前,这段代码是编译不通过的,因为a变量没加final。但是在jdk在1.8之后,不再强制你加final,而是编译器自动会给你补上final。

为什么java语法要求我们必需要用final修饰呢?

我们反编译一下,内部类的反编译代码Test$1.class

final class Test$1 implements Runnable {
    
    
    Test$1(int var1) {
    
    
        this.val$a = var1;
    }

    public void run() {
    
    
        System.out.println(this.val$a);
    }
}

反编译出来的代码有些省略,少了val$a变量的定义,这个不是重点,无所谓了。

以上反编译代码可以看出,匿名内部类之所以可以访问局部变量,是因为在底层将这个局部变量的值传入到了匿名内部类中,并且以匿名内部类的成员变量的形式存在,这个值的传递过程是通过匿名内部类的构造器完成的。

因为将数据拷贝完成后,如果不用final修饰,则原先的局部变量可以发生变化。如果局部变量发生变化后,匿名内部类是不知道的(因为他只是拷贝了局不变量的值,并不是直接使用的局部变量)。这里举个栗子:原先局部变量指向的是对象A,在创建匿名内部类后,匿名内部类中的成员变量也指向A对象。但过了一段时间局部变量的值指向另外一个B对象,但此时匿名内部类中还是指向原先的A对象。那么程序再接着运行下去,可能就会导致程序运行的结果与预期不同。

2,JVM对于声明为final的局部变量做了哪些性能优化?

这个问题比较复杂,R大已经对此做了比较详细的分析,有兴趣可以去看一下

[https://www.zhihu.com/question/21762917]:

结论是:在能够通过编译的前提下,无论局部变量声明时带不带final关键字修饰,对其访问的效率都一样。

扫描二维码关注公众号,回复: 12321599 查看本文章

二,finally

finally 是保证程序一定执行的机制,同样的它也是 Java 中的一个关键字,一般来讲,finally 一般不会单独使用,它一般和 try…finally 块或try…catch…finally一起使用。

  • finally 块只会在 try 块执行的情况下才执行,finally 不会单独存在。
  • finally 块在离开 try 块执行完成后或者 try 块未执行完成但是接下来是控制转移语句时(return/continue/break)在控制转移语句之前执行。
public class Test {
    
    
    public static void main(String[] args) {
    
    
        System.out.println(getNum());
    }
    public static int getNum() {
    
    
        int a = 0;
        try {
    
    
            a = 1;
            return a;
        } finally {
    
    
            ++a;
            System.out.println("finally执行");
        }
    }
}

运行输出:

finally执行
1
  • 如果 try 语句块中出现了属于 exception 及其子类的异常,则跳转到 catch 处理
  • 如果 try 语句块中出现了不属于 exception 及其子类的异常,则跳转到 finally 处理
  • 如果 catch 语句块中新出现了异常,则跳转到 finally 处理
  • 如果try…catch中有return语句,那么先暂存return值,待执行完finally之后,才会执行return返回值还是返回的暂存值。

三,finalize

Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在 Object 类中定义的,因此所有的类都继承了它。子类覆盖 finalize() 方法以整理系统资源或者执行其他清理工作。

finalize 的工作方式是这样的:一旦垃圾回收器准备好释放对象占用的存储空间,将会首先调用 finalize 方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。垃圾回收只与内存有关。

我们知道,Java 与 C++ 一个显著的区别在于 Java 能够自动管理内存,在 Java 中,由于 GC 的自动回收机制,因而并不能保证 finalize 方法会被及时地执行(垃圾对象的回收时机具有不确定性),也不能保证它们会被执行。

也就是说,finalize 的执行时期不确定,我们并不能依赖于 finalize 方法帮我们进行垃圾回收,可能出现的情况是在我们耗尽资源之前,gc 却仍未触发,所以推荐使用资源用完即显示释放的方式,比如 close 方法。除此之外,finalize 方法也会生吞异常。

所以我们在日常开发中并不提倡使用 finalize 方法

猜你喜欢

转载自blog.csdn.net/u013277209/article/details/109450934
今日推荐