深入理解final关键字(详解)

提及final关键字都会想到一个常见的面试题就是:
final、finally、finalize区别
在这里我们来简单回忆一下这三者的区别,

  • final: Final用于修饰类、成员变量和成员方法。final修饰的类,不能被继承(String、StringBuilder、StringBuffer、Math,不可变类),其中所有的方法都不能被重写(这里需要注意的是不能被重写,但是可以被重载,这里很多人会弄混),所以不能同时用abstract和final修饰类(abstract修饰的类是抽象类,抽象类是用于被子类继承的,和final起相反的作用);Final修饰的方法不能被重写,但是子类可以用父类中final修饰的方法;Final修饰的成员变量是不可变的,如果成员变量是基本数据类型,初始化之后成员变量的值不能被改变,如果成员变量是引用类型,那么它只能指向初始化时指向的那个对象,不能再指向别的对象,但是对象当中的内容是允许改变的。

我们来深入理解一下被final修饰的类、方法、变量

  1. final修饰的类
    在这里插入图片描述

可以看到 被final修饰的类被继承编译器就直接报错了,如果去掉final
在这里插入图片描述
就没有问题

那这里就有一个问题了,为什么设计了继承还要有final来破坏这种继承关系呢。
这个解释在《Java编程思想》说的比较清楚:
使用 final 方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的 Java 版本中,不需要使用 final 方法进行这些优化了。“

  1. 被final修饰的变量

我们来看一个有意思的代码

public class Main {
   public static void main(String[] args) {
       String a = "xiaomeng2";
       final String b = "xiaomeng";
       String d = "xiaomeng";
       String c = b + 2;
       String e = d + 2;
       System.out.println((a == c));
       System.out.println((a == e));
   }
}

这段代码的输出结果是什么呢?
答案是: true 和 false
原因:

  1. 变量a指的是字符串常量池中的 xiaomeng2;
  2. 变量 b 是 final 修饰的,变量 b 的值在编译时候就已经确定了它的确定值,换句话说就是提前知道了变量 b 的内容到底是个啥,相当于一个编译期常量;
  3. 变量 c 是 b + 2得到的,由于 b 是一个常量,所以在使用 b 的时候直接相当于使用 b 的原始值(xiaomeng)来进行计算,所以 c 生成的也是一个常量,a 是常量,c 也是常量,都是 xiaomeng2 而 Java 中常量池中只生成唯一的一个 xiaomeng2 字符串,所以 a 和 c 是相等的!
  4. d 是指向常量池中 xiaomeng,但由于 d 不是 final 修饰,也就是说在使用 d 的时候不会提前知道 d 的值是什么,所以在计算 e 的时候就不一样了,e的话由于使用的是 d 的引用计算,变量d的访问却需要在运行时通过链接来进行,所以这种计算会在堆上生成 xiaomeng2 ,所以最终 e 指向的是堆上的 xiaomeng2 , 所以 a 和 e 不相等。
    总得来说就是:a、c是常量池的xiaomeng2,e是堆上的xiaomeng2

然后我们来看一下被final修饰的引用变量和基本变量有什么不同:

我们看基本变量
在这里插入图片描述
可以看到 基本变量使用final修饰了就不可变了

对于引用变量被final修饰了:引用变量引用不可变,但是引用对象的内容可以改变。
在这里插入图片描述
可以看到使用final修饰了的类不能再指向别处
如何理解内容可变呢:
在这里插入图片描述
可以看到这里类的属性a的值是能够改变的

然后我们总结一下使用final关键字的好处:

  1. final方法比非final快一些
  2. final关键字提高了性能。JVM和Java应用都会缓存final变量。
  3. final变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销。
  4. 使用final关键字,JVM会对方法、变量及类进行优化。
  • Finally:

通常和try catch搭配使用,保证不管有没有发生异常,资源都能够被释放(释放连接、关闭IO流)

当try中有return时执行顺序:return语句并不是函数的最终出口,如果有finally语句,这在return之后还会执行finally(return的值会暂存在栈里面,等待finally执行后再返回)

  • 情况一(try中有return,finally中没有return)

在这里插入图片描述

“return num += 80”被拆分成了“num = num+80”和“return num”两个语句,线执行try中的“num =num+80”语句,将其保存起来,在try中的”return num“执行前,先将finally中的语句执行完,而后再将90返回。

  • 情况二(try和finally中均有return)

在这里插入图片描述

try中的return被”覆盖“掉了,不再执行。

  • 情况三(finally中改变返回值num)

在这里插入图片描述

虽然在finally中改变了返回值num,但因为finally中没有return该num的值,因此在执行完finally中的语句后,test()函数会得到try中返回的num的值,而try中的num的值依然是程序进入finally代码块前保留下来的值,因此得到的返回值为10。并且函数最后面的return语句不会执行。

  • 另外一种情况:将num的值包装在Num类中

在这里插入图片描述

总结:

try语句在返回前,将其他所有的操作执行完,保留好要返回的值,而后转入执行finally中的语句,而后分为以下三种情况:

情况一:如果finally中有return语句,则会将try中的return语句”覆盖“掉,直接执行finally中的return语句,得到返回值,这样便无法得到try之前保留好的返回值。

情况二:如果finally中没有return语句,也没有改变要返回值,则执行完finally中的语句后,会接着执行try中的return语句,返回之前保留的值。

情况三:如果finally中没有return语句,但是改变了要返回的值,这里有点类似与引用传递和值传递的区别,分以下两种情况:
如果return的数据是基本数据类型,则在finally中对该基本数据的改变不起作用,try中的return语句依然会返回进入finally块之前保留的值。
如果return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起作用,try中的return语句返回的就是在finally中改变后的该属性的值。


  • Finalize:

Finalize是object类中的一个方法,子类可以重写finalize()方法实现对资源的回收。垃圾回收只负责回收内存,并不负责资源的回收,资源回收要由程序员完成,Java虚拟机在垃圾回收之前会先调用垃圾对象的finalize方法用于使对象释放资源(如关闭连接、关闭文件),之后才进行垃圾回收,这个方法一般不会显示的调用,在垃圾回收时垃圾回收器会主动调用。

博客借鉴:
https://blog.csdn.net/CSDN_bang/article/details/86851078
https://blog.csdn.net/stypace/article/details/42102181

猜你喜欢

转载自blog.csdn.net/qq_42651904/article/details/87708198