分析一道奇葩面试题:final、finally与finalize的区别

1,997 阅读6分钟

记得刚开始工作的时候见过这个面试题,明明是毫不相干的三个关键词,每次面试总是把这三者放在一起,可能是它们名字非常像吧,今天让我们聊一聊final、finally、finalize这三个各自的使用场景。

在这里插入图片描述

另外本人整理了20年面试题大全,包含spring、并发、数据库、Redis、分布式、dubbo、JVM、微服务等方面总结,需要的话自行领取:腾讯文档

一、final

在java中,final可以用来修饰类,方法和变量(成员变量或局部变量)。下面将对其详细介绍。

1.1 修饰类

  当用final修饰类的时,表明该类不能被其他类所继承。当我们需要让一个类永远不被继承,此时就可以用final修饰,但要注意:

final类中所有的成员方法都会隐式的定义为final方法。

1.2 修饰方法

使用final方法的原因主要有两个:

  (1) 把方法锁定,以防止继承类对其进行更改。

  (2) 效率,在早期的java版本中,会将final方法转为内嵌调用。但若方法过于庞大,可能在性能上不会有多大提升。因此在最近版本中,不需要final方法进行这些优化了。

final方法意味着“最后的、最终的”含义,即此方法不能被重写。

注意:若父类中final方法的访问权限为private,将导致子类中不能直接继承该方法,因此,此时可以在子类中定义相同方法名的函数,此时不会与重写final的矛盾,而是在子类中重新地定义了新方法。

1.3 修饰变量

  final成员变量表示常量,只能被赋值一次,赋值后其值不再改变。类似于C++中的const。

  当final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化;如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。本质上是一回事,因为引用的值是一个地址,final要求值,即地址的值不发生变化。 

  final修饰一个成员变量(属性),必须要显示初始化。这里有两种初始化方式,一种是在变量声明的时候初始化;第二种方法是在声明变量的时候不赋初值,但是要在这个变量所在的类的所有的构造函数中对这个变量赋初值。

  当函数的参数类型声明为final时,说明该参数是只读型的。即你可以读取使用该参数,但是无法改变该参数的值。

二、 finally

 finally作为异常处理的一部分,它只能用在try/catch语句中,并且附带一个语句块,表示这段语句最终一定会被执行(不管有没有抛出异常),经常被用在需要释放资源的情况下。(×)(这句话其实存在一定的问题)

finally是异常处理中的一个关键字,通常的结构是这样的:

try{
return a;
 } catch(Exception ex){
return b;
 } finally{
return c;
 },

它一般用于资源释放,比如我们可以在finally块中关闭数据库连接,在这个结构中不管异常有没有发生finally中的代码都会执行。

但是finally中的代码不是一定会被执行。在以下情况下不会执行finally中的代码:

  1. 在进入try块之前程序发生异常。

  2. 在try块中调用了System.exit(0)终止了虚拟机的运行。

  3. 在try块或catch块中程序被中断,比如说死机。

我们再来看下面这个例子:

public class Demo02 {
public static void main(String[] args) {
 System.out.println(methd());
  }
public static int methd(){
 int num = 1;
  try{
   return num;
}catch (Exception ex){
   return 0;
}finally {
  num++;
   }
  }
 }

程序运行的结果为:1

这个例子中finally中的代码究竟是在return前还是return后执行的呢?你可能认为是在return 前执行的,我们来看一下程序执行的过程,执行到try块中的return num的时候num的值1会被作为返回值存放到栈中但是这时程序并不会返回,而是去执行num++,num的值变为2,然后程序返回结束,此时返回的还是栈中的num值1。

我们再来看另一个例子:

public classDemo02 {
 public static voidmain(String[] args) {
  System.out.println(methd());
   }
 public static intmethd(){
  intnum = 1;
 try{
  returnnum;
}catch(Exception ex){
  return0;
}finally{
  return2;
   }
  }
 }

这段程序的运行结果为2

在程序执行到try块中的return num时将num中的值1作为返回值保存在栈中,然后执行finally块中的代码,return 2,此时栈中的返回值变为了2,所以最后的结果为2.

总结一下:

  1. finally在try块和catch块return执行后,返回前执行。

  2. 如果finally中没有return,则其执行结果不影响try和catch中已确定的返回值。

  3. 如果finally中有return,则其执行结果会直接返回。

三、finalize

 finalize()是在java.lang.Object里定义的,也就是说每一个对象都有这么个方法。这个方法在gc启动,该对象被回收的时候被调用。其实gc可以回收大部分的对象(凡是new出来的对象,gc都能搞定,一般情况下我们又不会用new以外的方式去创建对象),所以一般是不需要程序员去实现finalize的。

特殊情况下,需要程序员实现finalize,当对象被回收的时候释放一些资源,比如:一个socket链接,在对象初始化时创建,整个生命周期内有效,那么就需要实现finalize,关闭这个链接。

  使用finalize还需要注意一个事,调用super.finalize();

  一个对象的finalize()方法只会被调用一次,而且finalize()被调用不意味着gc会立即回收该对象,所以有可能调用finalize()后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会调用finalize(),产生问题。 所以,推荐不要使用finalize()方法,它跟析构函数不一样。

最后:

针对最近很多人都在面试,我这边也整理了相当多的面试专题资料,也有其他大厂的面经。希望可以帮助到大家。

面试题答案都整理成文档笔记。也还整理了一些面试资料&最新2020收集的一些大厂的面试真题(都整理成文档,小部分截图),有需要的可以自行领取