【Java面试题系列】:Java中final finally finalize的区别

本篇为【Java面试题系列】第三篇,文中如有错误,欢迎指正。第一篇链接:【Java面试题系列】:Java基础知识常见面试题汇总第一篇第二篇链接:【Java面试题系列】:Java基础知识常见面试题汇总第二篇按我的个人理解,这个题目本身就问的有点问题,因为这3个关键字之间没啥关系,是相对独立的,我猜想这道题的初衷应该是想了解面试者对Java中finalfinallyfinalize的使用方法的掌握情况...

【Java面试题系列】:Java中final finally finalize的区别

本篇为【Java面试题系列】第三篇,文中如有错误,欢迎指正。

第一篇链接:【Java面试题系列】:Java基础知识常见面试题汇总 第一篇

第二篇链接:【Java面试题系列】:Java基础知识常见面试题汇总 第二篇

按我的个人理解,这个题目本身就问的有点问题,因为这3个关键字之间没啥关系,是相对独立的,我猜想这道题的初衷应该是想了解面试者对Java中final finally finalize的使用方法的掌握情况,只是因为3个关键字比较像,而成了现在网上流传的题目“Java中final finally finalize的区别”。

既然是想了解面试者对Java中final finally finalize的使用方法的掌握情况,那么本篇我们就分别讲解下final,finally,finalize的使用方法。

1.final用法

我们先看下final的英文释义:最终的;决定性的;不可更改的,不禁要推测被final修饰的变量,方法或者类是不是不可修改的呢?

1.1final修饰类

在Java中,被final修饰的类,不能被继承,也就是final类的成员方法没有机会被继承,也没有机会被重写。

在设计类的时候,如果这个类不需要有子类,类的实现细节不允许改变,那么就可以设计为final类。

如我们在开发中经常使用的String类就是final类,以下为部分源码:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence { ......} 

1.2final修饰方法

在Java中,被final修饰的方法,可以被继承,但不能被子类重写(覆盖)。

在设计方法时,如果这个方法不希望被子类重写(覆盖),那么就可以设计为final方法。

举个具体的例子,我们新建个父类Animal如下:

package com.zwwhnly.springbootdemo;public class Animal { public void eat() {  System.out.println(“Animal eat.“); } public void call() {  System.out.println(“Animal call.“); } public final void fly() {  System.out.println(“Animal fly.“); } private final void swim() {  System.out.println(“Animal swim.“); }}

然后定义一个子类Cat继承Animal类,代码如下:

package com.zwwhnly.springbootdemo;public class Cat extends Animal { @Override public void eat() {  System.out.println(“Cat eat.“); } @Override public void fly() {  System.out.println(“Cat fly.“); } public static void main(String[] args) {  Cat cat = new Cat();  cat.eat();  cat.call();  cat.fly();  cat.swim(); }}

我们会发现,以上代码中有2个错误

1)当我们重写fly()方法时,因为父类的fly()方法被定义为final方法,重写时会编译错误

2)cat.swim();报错,因为父类的swim()方法被定义为private,子类是继承不到的

然后我们将报错的代码删除,运行结果如下:

Cat eat.

Animal call.

Animal fly.

也就是eat()方法被子类重写了,继承了父类的成员方法call()和final方法fly()。

但是值得注意的是,在子类Cat中,我们是可以重新定义父类的私有final方法swim()的,不过此时明显不是重写(你可以加@Override试试,会编译报错),而是子类自己的成员方法swim()。

package com.zwwhnly.springbootdemo;public class Cat extends Animal { @Override public void eat() {  System.out.println(“Cat eat.“); } public void swim() {  System.out.println(“Cat swim.“); } public static void main(String[] args) {  Cat cat = new Cat();  cat.eat();  cat.call();  cat.fly();  cat.swim(); }}

此时的运行结果为:

Cat eat.

Animal call.

Animal fly.

Cat swim.

1.3final修饰成员变量

用final修饰的成员变量没有默认值,可以在声明时赋值或者在构造函数中赋值,但必须赋值且只能被赋值1次,赋值后无法修改。

我们修改下1.2中的Cat类代码,定义2个final成员变量,1个声明完立即赋值,1个在构造函数中赋值:

package com.zwwhnly.springbootdemo;public class Cat extends Animal { private final int age = 1; private final String name; public Cat(String name) {  this.name = name; } @Override public void eat() {  System.out.println(“Cat eat.“); } public static void main(String[] args) {  Cat whiteCat = new Cat(“小白“);  whiteCat.age = 2;  System.out.println(whiteCat.age);  System.out.println(whiteCat.name);  Cat blackCat = new Cat(“小黑“);  blackCat.name = “小黑猫“;  System.out.println(blackCat.age);  System.out.println(blackCat.name); }}

以上代码有2个编译错误,1个是whiteCat.age = 2;修改成员变量age时,另1个是blackCat.name = “小黑猫“;修改成员变量name时,都提示不能修改final成员变量。

删除掉错误的代码,运行结果如下:

1

小白

1

小黑

1.4final修饰局部变量

被final修饰的局部变量,既可以在声明时立即赋值,也可以先声明,后赋值,但只能赋值一次,不可以重复赋值。

修改下Cat类的eat()方法如下:

@Overridepublic void eat() { final String breakfast; final String lunch = “午餐“; breakfast = “早餐“; lunch = “午餐2“; breakfast = “早餐2“; System.out.println(“Cat eat.“);}

以上代码中2个错误,1个是lunch = “午餐2“;,1个是breakfast = “早餐2“;,都是对final局部变量第2次赋值时报错。

1.5final修饰方法参数

方法参数其实也是局部变量,因此final修饰方法参数和1.4中final修饰局部变量的使用类似,即方法中只能使用方法的参数值,但不能修改参数值。

在Cat类中新增方法printCatName,将方法参数修饰为final:

public static void main(String[] args) { Cat whiteCat = new Cat(“小白“); whiteCat.printCatName(whiteCat.name);}public void printCatName(final String catName) { //catName = “修改catName“; // 该行语句会报错 System.out.println(catName);}

运行结果:

小白

2.finally用法

提起finally,大家都知道,这是Java中处理异常的,通常和try,catch一起使用,主要作用是不管代码发不发生异常,都会保证finally中的语句块被执行。

你是这样认为的吗?说实话,哈哈。

那么问题来了,finally语句块一定会被执行吗?,答案是不一定

让我们通过具体的示例来证明该结论。

2.1在 try 语句块之前返回(return)或者抛出异常,finally不会被执行

package com.zwwhnly.springbootdemo;public class FinallyTest { public static void main(String[] args) {  System.out.println(“return value of test():“test()); } public static int test() {  int i = 1;  /*if (i == 1) {return 0;  }*/  System.out.println(“the previous statement of try block“);  i = i / 0;  try {System.out.println(“try block“);return i;  } finally {System.out.println(“finally block“);  } }}

运行结果如下:

也就是说,以上示例中,finally语句块没有被执行。

然后我们将上例中注释的代码取消注释,此时运行结果为:

return value of test():0

finally语句块还是没有被执行,因此,我们可以得出结论:

只有与 finally 相对应的 try 语句块得到执行的情况下,finally 语句块才会执行。

以上两种情况,都是在 try 语句块之前返回(return)或者抛出异常,所以 try 对应的 finally 语句块没有执行。

2.2与 finally 相对应的 try 语句块得到执行,finally不一定会被执行

那么,与 finally 相对应的 try 语句块得到执行的情况下,finally 语句块一定会执行吗?答案仍然是不一定。

看下下面这个例子:

package com.zwwhnly.springbootdemo;public class FinallyTest { public static void main(String[] args) {  System.out.println(“return value of test():“test()); } public static int test() {  int i = 1;  try {System.out.println(“try block“);System.exit(0);return i;  } finally {System.out.println(“finally block“);  } }}

运行结果为:

try block

finally语句块还是没有被执行,为什么呢?因为我们在try语句块中执行了System.exit(0);,终止了Java虚拟机的运行。当然,一般情况下,我们的应用程序中是不会调用System.exit(0);的,那么,如果不调用这个方法,finally语句块一定会被执行吗?

答案当然还是不一定,当一个线程在执行 try 语句块或者 catch 语句块时被打断(interrupted)或者被终止(killed),与其相对应的 finally 语句块可能不会执行。还有更极端的情况,就是在线程运行 try 语句块或者 catch 语句块时,突然死机或者断电,finally 语句块肯定不会执行了。当然,死机或者断电属于极端情况,在这里只是为了证明,finally语句块不一定会被执行。

2.3try语句块或者catch语句块中有return语句

如果try语句块中有return语句, 是return语句先执行还是finally语句块先执行呢?

带着这个问题,我们看下如下这个例子:

pac
源文地址:https://www.guoxiongfei.cn/cntech/17059.html