我把自己以往的文章汇总成为了 Github ,欢迎各位大佬 star

https://github.com/crisxuan/bestJavaer

已提交此篇文章

final 是 Java 中的关键字,它也是 Java 中很重要的一个关键字,final 修饰的类、方法、变量有不同的含义;finally 也是一个关键字,不过我们可以使用 finally 和其他关键字结合做一些组合操作; finalize 是一个不让人待见的方法,它是对象祖宗 Object 中的一个方法,finalize 机制现在已经不推荐使用了。本篇文章,cxuan 就带你从这三个关键字入手,带你从用法、应用、原理的角度带你深入浅出理解这三个关键字。

final、finally 和 finalize

我相信在座的各位都是资深程序员,final 这种基础关键字就不用多说了。不过,还是要照顾一下小白读者,毕竟我们都是从小白走过来的嘛。

final 修饰类、属性和方法

final 可以用来修饰类,final 修饰的类不允许其他类继承,也就是说,final 修饰的类是独一无二的。如下所示

我们首先定义了一个 FinalUsage 类,它使用 final 修饰,同时我们又定义了一个 FinalUsageExtend 类,它想要继承(extend) FinalUsage,我们如上继承后,编译器不让我们这么玩儿,它提示我们 不能从 FinalUsage 类继承,为什么呢?不用管,这是 Java 的约定,有一些为什么没有必要,遵守就行。

final 可以用来修饰方法,final 修饰的方法不允许被重写,我们先演示一下不用 final 关键字修饰的情况

如上图所示,我们使用 FinalUsageExtend 类继承了 FinalUsage 类,并提供了 writeArticle 方法的重写。这样编译是没有问题的,重写的关键点是 @Override 注解和方法修饰符、名称、返回值的一致性。

注意:很多程序员在重写方法的时候都会忽略 @Override,这样其实无疑增加了代码阅读的难度,不建议这样。

当我们使用 final 修饰方法后,这个方法则不能被重写,如下所示

当我们把 writeArticle 方法声明为 void 后,重写的方法会报错,无法重写 writeArticle 方法。

final 可以修饰变量,final 修饰的变量一经定义后就不能被修改,如下所示

编译器提示的错误正是不能继承一个被 final 修饰的类。

我们上面使用的是字符串 String ,String 默认就是 final 的,其实用不用 final 修饰意义不大,因为字符串本来就不能被改写,这并不能说明问题。

我们改写一下,使用基本数据类型来演示

同样的可以看到,编译器仍然给出了 age 不能被改写的提示,由此可以证明,final 修饰的变量不能被重写。

在 Java 中不仅仅只有基本数据类型,还有引用数据类型,那么引用类型被 final 修饰后会如何呢?我们看一下下面的代码

首先构造一个 Person

public class Person {
int id;
String name;
get() and set() ...
toString()...
}

然后我们定义一个 final 的 Person 变量。

static final Person person = new Person(25,"cxuan");

public static void main(String[] args) {

  System.out.println(person);
person.setId(26);
person.setName("cxuan001");
System.out.println(person);
}

输出一下,你会发现一个奇怪的现象,为什么我们明明改了 person 中的 id 和 name ,编译器却没有报错呢?

这是因为,final 修饰的引用类型,只是保证对象的引用不会改变。对象内部的数据可以改变。这就涉及到对象在内存中的分配问题,我们后面再说。

finally 保证程序一定被执行

finally 是保证程序一定执行的机制,同样的它也是 Java 中的一个关键字,一般来讲,finally 一般不会单独使用,它一般和 try 块一起使用,例如下面是一段 try...finally 代码块

try{
lock.lock();
}finally {
lock.unlock();
}

这是一段加锁/解锁的代码示例,在 lock 加锁后,在 finally 中执行解锁操作,因为 finally 能够保证代码一定被执行,所以一般都会把一些比较重要的代码放在 finally 中,例如解锁操作、流关闭操作、连接释放操作等。

当 lock.lock() 产生异常时还可以和 try...catch...finally 一起使用

try{
lock.lock();
}catch(Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}

try...finally 这种写法适用于 JDK1.7 之前,在 JDK1.7 中引入了一种新的关闭流的操作,那就是 try...with...resources,Java 引入了 try-with-resources 声明,将 try-catch-finally 简化为 try-catch,这其实是一种语法糖,并不是多了一种语法。try...with...resources 在编译时还是会进行转化为 try-catch-finally 语句。

语法糖(Syntactic sugar),也译为糖衣语法,是指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。

在 Java 中,有一些为了简化程序员使用的语法糖,后面有机会我们再谈。

finalize 的作用

finalize 是祖宗类 Object类的一个方法,它的设计目的是保证对象在垃圾收集前完成特定资源的回收。finalize 现在已经不再推荐使用,在 JDK 1.9 中已经明确的被标记为 deprecated

深入理解 final 、finally 和 finalize

final 设计

许多编程语言都会有某种方法来告知编译器,某一块数据是恒定不变的。有时候恒定不变的数据很有用,比如

  • 一个永不改变的编译期常量 。例如 static final int num = 1024
  • 一个运行时被初始化的值,而且你不希望改变它

final 的设计会和 abstract 的设计产生冲突,因为 abstract 关键字主要修饰抽象类,而抽象类需要被具体的类所实现。final 表示禁止继承,也就不会存在被实现的问题。因为只有继承后,子类才可以实现父类的方法。

类中的所有 private 都隐式的指定为 final 的,在 private 修饰的代码中使用 final 并没有额外的意义。

空白 final

Java 是允许空白 final 的,空白 final 指的是声明为 final ,但是却没有对其赋值使其初始化。但是无论如何,编译器都需要初始化 final,所以这个初始化的任务就交给了构造器来完成,空白 final 给 final 提供了更大的灵活性。如下代码

public class FinalTest {

   final Integer finalNum;

   public FinalTest(){
finalNum = 11;
} public FinalTest(int num){
finalNum = num;
} public static void main(String[] args) {
new FinalTest();
new FinalTest(25);
}
}

在不同的构造器中对不同的 final 进行初始化,使 finalNum 的使用更加灵活。

使用 final 的方法主要有两个:不可变效率

  • 不可变:不可变说的是把方法锁定(注意不是加锁),重在防止其他方法重写。
  • 效率:这个主要是针对 Java 早期版本来说的,在 Java 早期实现中,如果将方法声明为 final 的,就是同意编译器将对此方法的调用改为内嵌调用,但是却没有带来显著的性能优化。这种调用就比较鸡肋,在 Java5/6 中,hotspot 虚拟机会自动探测到内嵌调用,并把它们优化掉,所以使用 final 修饰的方法就主要有一个:不可变。

注意:final 不是 Immutable 的,Immutable 才是真正的不可变。

final 不是真正的 Immutable,因为 final 关键字引用的对象是可以改变的。如果我们真的希望对象不可变,通常需要相应的类支持不可变行为,比如下面这段代码

final List<String> fList = new ArrayList();
fList.add("Hello");
fList.add("World");
List unmodfiableList = List.of("hello","world");
unmodfiableList.add("again");

List.of 方法创建的就是不可变的 List。不可变 Immutable 在很多情况下是很好的选择,一般来说,实现 Immutable 需要注意如下几点

  • 将类声明为 final,防止其他类进行扩展。
  • 将类内部的成员变量(包括实例变量和类变量)声明为 privatefinal 的,不要提供可以修改成员变量的方法,也就是 setter 方法。
  • 在构造对象时,通常使用 deep-clone ,这样有助于防止在直接对对象赋值时,其他人对输入对象的修改。
  • 坚持 copy-on-write 原则,创建私有的拷贝。

final 能提高性能吗?

final 能否提高性能一直是业界争论的点,很多

看完这篇 final、finally 和 finalize 和面试官扯皮就没问题了的更多相关文章

  1. 看完这篇Exception 和 Error,和面试官扯皮就没问题了

    在 Java 中的基本理念是 结构不佳的代码不能运行,发现错误的理想时期是在编译期间,因为你不用运行程序,只是凭借着对 Java 基本理念的理解就能发现问题.但是编译期并不能找出所有的问题,有一些 N ...

  2. 看完这篇HTTP,跟面试官扯皮就没问题了

    我是一名程序员,我的主要编程语言是 Java,我更是一名 Web 开发人员,所以我必须要了解 HTTP,所以本篇文章就来带你从 HTTP 入门到进阶,看完让你有一种恍然大悟.醍醐灌顶的感觉. 最初在有 ...

  3. 看完这篇 HTTPS,和面试官扯皮就没问题了

    下面我们来一起学习一下 HTTPS ,首先问你一个问题,为什么有了 HTTP 之后,还需要有 HTTPS ?我突然有个想法,为什么我们面试的时候需要回答标准答案呢?为什么我们不说出我们自己的想法和见解 ...

  4. 看完这篇 Session、Cookie、Token,和面试官扯皮就没问题了

    Cookie 和 Session HTTP 协议是一种无状态协议,即每次服务端接收到客户端的请求时,都是一个全新的请求,服务器并不知道客户端的历史请求记录:Session 和 Cookie 的主要目的 ...

  5. 看完这篇 HashMap,和面试官扯皮就没问题了

    HashMap 概述 如果你没有时间细抠本文,可以直接看 HashMap 概述,能让你对 HashMap 有个大致的了解. HashMap 是 Map 接口的实现,HashMap 允许空的 key-v ...

  6. 看完这篇还不会 GestureDetector 手势检测,我跪搓衣板!

    引言 在 android 开发过程中,我们经常需要对一些手势,如:单击.双击.长按.滑动.缩放等,进行监测.这时也就引出了手势监测的概念,所谓的手势监测,说白了就是对于 GestureDetector ...

  7. 看完这篇Redis缓存三大问题,保你面试能造火箭,工作能拧螺丝。

    前言 日常的开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题. 一旦涉及大数据量的需求,如一些商品抢购的情景,或者主页访问量瞬间较 ...

  8. APP的缓存文件到底应该存在哪?看完这篇文章你应该就自己清楚了

    APP的缓存文件到底应该存在哪?看完这篇文章你应该就自己清楚了 彻底理解android中的内部存储与外部存储 存储在内部还是外部 所有的Android设备均有两个文件存储区域:"intern ...

  9. 关于 Docker 镜像的操作,看完这篇就够啦 !(下)

    紧接着上篇<关于 Docker 镜像的操作,看完这篇就够啦 !(上)>,奉上下篇 !!! 镜像作为 Docker 三大核心概念中最重要的一个关键词,它有很多操作,是您想学习容器技术不得不掌 ...

随机推荐

  1. 从四个问题透析Linux下C++编译&链接

    摘要:编译&链接对C&C++程序员既熟悉又陌生,熟悉在于每份代码都要经历编译&链接过程,陌生在于大部分人并不会刻意关注编译&链接的原理.本文通过开发过程中碰到的四个典型 ...

  2. 过万 star 高星项目的秘密——GitHub 热点速览 Vol.39

    作者:HelloGitHub-小鱼干 虽然国外十一并不过国庆,但是本周的 GitHub 也稍显疲软,GitHub 周榜的获 star 超过 1k 的项目寥寥无几,本周新开源的项目更是屈指可数.用 C ...

  3. netty第一讲 创建

    1.新建一个maven项目  https://blog.csdn.net/yanghaibobo110/article/details/73835469 2.netty是什么玩意 官方那个给出的介绍是 ...

  4. vue安装教程

    Vue.js 安装教程 安装node.js https://nodejs.org/zh-cn/download/ 选择一个适合自己电脑的版本下载 下载成功, 直接安装, 全部点击下一步 然后输入 黑窗 ...

  5. LeetCode刷题总结-数学篇

    本文总结LeetCode上有数学类的算法题,推荐刷题总数为40道.具体考点分析如下图: 1.基本运算问题 题号:29. 两数相除,难度中等 题号:166. 分数到小数,难度中等 题号:372. 超级次 ...

  6. 【学习笔记/题解】分层图/[JLOI2011]飞行路线

    题目戳我 \(\text{Solution:}\) 关于分层图: 一般用于处理:给你\(k\)次机会对边权进行修改的最短路问题. 算法流程: 建立出\(k\)层图,对应进行\(k\)次操作后的局面. ...

  7. 【题解】Bzoj3916

    字符串\(Hash\). 笔者实在太菜了,到现在还没有熟练掌握\(Hash\),就来这里写一篇学习笔记. \(Description\) 有三个好朋友喜欢在一起玩游戏,\(A\)君写下一个字符串\(S ...

  8. javascript之判断数组的几种方法

    今天和小伙伴一起出去吃饭,有个小伙伴突然问我,你是前端是吧,问一下现在前端判断数组都有哪些方法,哈哈不知道是不是考我,当时没有说全,吃过饭后看了下自己以前的小笔记这里总结一下目前知道的所有对于数组的判 ...

  9. UDP协议网络Socket编程(java实现C/S通信案例)

    我的博客园:https://www.cnblogs.com/chenzhenhong/p/13825286.html 我的CSDN博客:https://blog.csdn.net/Charzous/a ...

  10. GIT之分支管理

    分支管理 一.分支推进 主分支 单线分支,随着代码的提交而形成的一条直线,HEAD 随着commit提交之后的节点移动而移动. 子分支 当切换到子分支的时候,HEAD 则指向子分支的节点. 在子分支上 ...