本篇博客讲解1个常见的面试题:Java中final finally finalize的区别。

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

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

1. final用法

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

1.1 final修饰类

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

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

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

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

1.2 final修饰方法

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

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

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

package com.zwwhnly.springbootaction;

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.springbootaction;

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.springbootaction;

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.3 final修饰成员变量

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

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

package com.zwwhnly.springbootaction;

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.4 final修饰局部变量

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

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

@Override
public 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.5 final修饰方法参数

方法参数其实也是局部变量,因此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.springbootaction;

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.springbootaction;

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.3 try语句块或者catch语句块中有return语句

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

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

package com.zwwhnly.springbootaction;

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

运行结果:

try block

finally block

结论:finally 语句块在 try 语句块中的 return 语句之前执行。

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

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

package com.zwwhnly.springbootaction;

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");
i = i / 0;
return 1;
} catch (Exception e) {
System.out.println("catch block");
return 2;
} finally {
System.out.println("finally block");
}
}
}

运行结果:

try block

catch block

finally block

return value of test():2

结论:finally 语句块在 catch 语句块中的 return 语句之前执行。

通过上面2个例子,我们可以看出,其实 finally 语句块是在 try 或者 catch 中的 return 语句之前执行的。更加一般的说法是,finally 语句块应该是在控制转移语句之前执行,控制转移语句除了 return 外,还有 break ,continue和throw。

2.4 其它几个例子

示例1:

package com.zwwhnly.springbootaction;

public class FinallyTest {
public static void main(String[] args) {
System.out.println("return value of getValue():" + getValue());
} public static int getValue() {
try {
return 0;
} finally {
return 1;
}
}
}

运行结果:

return value of getValue():1

示例2:

package com.zwwhnly.springbootaction;

public class FinallyTest {
public static void main(String[] args) {
System.out.println("return value of getValue():" + getValue());
} public static int getValue() {
int i = 1;
try {
return i;
} finally {
i++;
}
}
}

运行结果:

return value of getValue():1

也许你会好奇,应该会返回2,怎么返回1了呢?可以借鉴下以下内容来理解(牵扯到了Java虚拟机如何编译finally语句块):

实际上,Java 虚拟机会把 finally 语句块作为 subroutine(对于这个 subroutine 不知该如何翻译为好,干脆就不翻译了,免得产生歧义和误解。)直接插入到 try 语句块或者 catch 语句块的控制转移语句之前。但是,还有另外一个不可忽视的因素,那就是在执行 subroutine(也就是 finally 语句块)之前,try 或者 catch 语句块会保留其返回值到本地变量表(Local Variable Table)中。待 subroutine 执行完毕之后,再恢复保留的返回值到操作数栈中,然后通过 return 或者 throw 语句将其返回给该方法的调用者(invoker)。请注意,前文中我们曾经提到过 return、throw 和 break、continue 的区别,对于这条规则(保留返回值),只适用于 return 和 throw 语句,不适用于 break 和 continue 语句,因为它们根本就没有返回值。

示例3:

package com.zwwhnly.springbootaction;

public class FinallyTest {
public static void main(String[] args) {
System.out.println("return value of getValue():" + getValue());
} public static int getValue() {
int i = 1;
try {
i = 4;
} finally {
i++;
return i;
}
}
}

运行结果:

return value of getValue():5

示例4:

package com.zwwhnly.springbootaction;

public class FinallyTest {
public static void main(String[] args) {
System.out.println("return value of getValue():" + getValue());
} public static int getValue() {
int i = 1;
try {
i = 4;
} finally {
i++;
}
return i;
}
}

运行结果:

return value of getValue():5

示例5:

package com.zwwhnly.springbootaction;

public class FinallyTest {
public static void main(String[] args) {
System.out.println(test());
} public static String test() {
try {
System.out.println("try block");
return test1();
} finally {
System.out.println("finally block");
}
} public static String test1() {
System.out.println("return statement");
return "after return";
}
}

try block

return statement

finally block

after return

2.5 总结

  1. finally语句块不一定会被执行
  2. finally语句块在 try语句块中的return 语句之前执行。
  3. finally语句块在 catch语句块中的return 语句之前执行。
  4. 注意控制转移语句 return ,break ,continue,throw对执行顺序的影响

3. finalize用法

finalize()是Object类的一个方法,因此所有的类都继承了这个方法。

protected void finalize() throws Throwable { }

finalize()主要用于在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。

当垃圾回收器(GC)决定回收某对象时,就会运行该对象的finalize()方法。

不过在Java中,如果内存总是充足的,那么垃圾回收可能永远不会进行,也就是说filalize()可能永远不被执行,显然指望它做收尾工作是靠不住的。

4. 参考

java中的final如何使用和理解

Java中的final关键字

解析Java finally

java finalize方法的使用

原创不易,如果觉得文章能学到东西的话,欢迎点个赞、评个论、关个注,这是我坚持写作的最大动力。

如果有兴趣,欢迎添加我的微信:zwwhnly,等你来聊技术、职场、工作等话题(PS:我是一名奋斗在上海的程序员)。

【Java面试题系列】:Java中final finally finalize的区别的更多相关文章

  1. Java面试题系列 ----- Java基础面试题(91道)

    更多详情点击查看,点这里!这里!!这里!!! 文末获取所有面试PDF文档! Java概述 1. 何为编程 编程就是让计算机为解决某个问题而使用某种程序设计语言编写程序代码,并最终得到结果的过程. 为了 ...

  2. java面试题系列12

    1.面向对象的特征有哪些方面 a.抽象: 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面.抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节.抽象 ...

  3. 【JAVA面试题系列一】面试题总汇--JAVA基础部分

    JAVA基础 基础部分的顺序: 基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法 线程的语法,集合的语法,io 的语法,虚拟机方面的语法 每天几道,持续更新!! 1.一个". ...

  4. [原]Java面试题-将字符串中数字提取出来排序后输出

    [Title][原]Java面试题-将字符串中数字提取出来排序后输出 [Date]2013-09-15 [Abstract]很简单的面试题,要求现场在纸上写出来. [Keywords]面试.Java. ...

  5. 最全最新java面试题系列全家桶(带答案)

    最全最新java面试题系列全家桶(带答案) 置顶 2019年04月06日 22:40:28 青春季风暴 阅读数 14082 文章标签: java面试题技术栈 更多 分类专栏: 面试   版权声明:本文 ...

  6. Android源码中final关键字的用法及final,finally,finalize的区别

    Android开发的学习流程 final,finally,finalize的区别 Android的发展越来越快,Android开发人员越来越多,当两种情况碰撞,在诸多开发者中跟紧Android步伐脱颖 ...

  7. 【转载】常见面试题:C#中String和string的区别分析

    在很多人面试C#开发工程师的时候,会遇到一个面试题,就是C#中String和string有啥区别.其实针对这个问题C#中String和string没有本质上的区别,两者在程序中都可使用,稍微的一个区别 ...

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

    文中面试题从茫茫网海中精心筛选,如有错误,欢迎指正! 1.前言 ​ 参加过社招的同学都了解,进入一家公司面试开发岗位时,填写完个人信息后,一般都会让先做一份笔试题,然后公司会根据笔试题的回答结果,确定 ...

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

    文中面试题从茫茫网海中精心筛选,如有错误,欢迎指正! 第一篇链接:[Java面试题系列]:Java基础知识常见面试题汇总 第一篇 1.JDK,JRE,JVM三者之间的联系和区别 你是否考虑过我们写的x ...

随机推荐

  1. Mysql Java 驱动安装

    怎么安装MYSQL的JDBC驱动 1.下载mysql for jdbc driver. http://dev.mysql.com/downloads/connector/j/5.0.html 2.解压 ...

  2. linux用户管理与用户组的重要文件

    用户管理的2个重要文件:/etc/passwd和/etc/shadow. /etc/passwd文件里存放的是用户的信息,其中不包含密码:passwd文件中每一行代表一个用户,且每一行分为7个字段使用 ...

  3. 英语发音规则---字母组合oo的发音规律

    英语发音规则---字母组合oo的发音规律 一.总结 一句话总结:在英语单词中,字母组合oo多数读长音/u:/,少数读短音/ʊ/.另外,还有极少数的特殊情况读/ʌ/, 在英语单词中,字母组合oo多数读长 ...

  4. C++STL 常用 函数 用法(转)

    http://www.cnblogs.com/duoduo369/archive/2012/04/12/2439118.html 迭代器(iterator) 个人理解就是把所有和迭代有关的东西给抽象出 ...

  5. scanf和cin的返回值

    需要连续从标准输入读取数据时,可以采用下面两种不同的方式判断文件结束: [cpp] view plaincopy   int i; while(scanf("%d",&i) ...

  6. getline()函数详解 (2013-03-26 17:19:58)

     学习C++的同学可能都会遇到一个getline()函数,譬如在C++premer中,标准string类型第二小节就是“用getline读取整行文本”.书上给的程序如下: int main() {   ...

  7. STL stl_construct.h

    stl_construct.h // Filename: stl_construct.h // Comment By: 凝霜 // E-mail: mdl2009@vip.qq.com // Blog ...

  8. Convolutional Neural Networks for Visual Recognition 4

    Modeling one neuron 下面我们开始介绍神经网络,我们先从最简单的一个神经元的情况开始,一个简单的神经元包括输入,激励函数以及输出.如下图所示: 一个神经元类似一个线性分类器,如果激励 ...

  9. 【leetcode刷题笔记】Gas Station

    There are N gas stations along a circular route, where the amount of gas at station i is gas[i]. You ...

  10. swiper轮播 swiper整屏轮播

    近期坐了几个移动端 整屏轮播的  效果 之前都是自己一个个写,之前听说过swiper插件,没有使用过,今天一尝试,果然,爽 使用方法示例 <div class="swiper-cont ...