我把本人以往的文章汇总成为了 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 可能用于帮忙 JVM 将办法进行内联,能够革新????编译器进行????编译????的能力等等,但这些论断很多都是基于假如作出的。

或者 R 大这篇答复会给咱们一些论断 https://www.zhihu.com/question/21762917

大抵说的就是无论局部变量申明时带不带 final 关键字润饰,对其拜访的效率都一样

比方上面这段代码(不带 final 的版本)

static int foo() {  int a = someValueA();  int b = someValueB();  return a + b; // 这里拜访局部变量} 

带 final 的版本

static int foo() {  final int a = someValueA();  final int b = someValueB();  return a + b; // 这里拜访局部变量} 

应用 javac 编译后得进去的后果一摸一样。

invokestatic someValueA:()Iistore_0 // 设置a的值invokestatic someValueB:()Iistore_1 // 设置b的值iload_0  // 读取a的值iload_1  // 读取b的值iaddireturn 

因为下面是应用援用类型,所以字节码雷同。

如果是常量类型,咱们看一下

// 带 finalstatic int foo(){  final int a = 11;  final int b = 12;  return a + b;}// 不带 finalstatic int foo(){  int a = 11;  int b = 12;  return a + b;} 

咱们别离编译一下两个 foo 办法,会发现如下字节码

右边是非 final 关键字润饰的代码,左边是有 final 关键字润饰的代码,比照这两个字节码,能够得出如下论断。

  • 不论有没有 final 润饰 ,int a = 11 或者 int a = 12 都当作常量对待。
  • 在 return 返回处,不加 final 的 a + b 会当作变量来解决;加 final 润饰的 a + b 会间接当作常量解决。
其实这种层面上的差别只对比拟繁难的 JVM 影响较大,因为这样的 VM 对解释器的依赖较大,本来 Class 文件里的字节码是怎么的它就怎么执行;对高性能的 JVM(例如 HotSpot、J9 等)则没啥影响。

所以,大部分 final 对性能优化的影响,能够间接疏忽,咱们应用 final 更多的考量在于其不可变性。

深刻了解 finally

咱们下面大抵聊到了 finally 的应用,其作用就是保障在 try 块中的代码执行实现之后,必然会执行 finally 中的语句。不论 try 块中是否抛出异样。

那么上面咱们就来深刻认识一下 finally ,以及 finally 的字节码是什么,以及 finally 到底何时执行的实质。

  • 首先咱们晓得 finally 块只会在 try 块执行的状况下才执行,finally 不会独自存在

这个不必再过多解释,这是大家都晓得的一条规定。finally 必须和 try 块或者 try catch 块一起应用。

  • 其次,finally 块在来到 try 块执行实现后或者 try 块未执行实现然而接下来是管制转移语句时(return/continue/break)在管制转移语句之前执行

这一条其实是阐明 finally 的执行机会的,咱们以 return 为例来看一下是不是这么回事。

如下这段代码

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

从执行后果能够证实是 finally 要先于 return 执行的。

当 finally 有返回值时,会间接返回。不会再去返回 try 或者 catch 中的返回值。

static int mayThrowException(){  try{    return 1;  }finally {    return 2;  }}public static void main(String[] args) {  System.out.println(FinallyTest.mayThrowException());} 
  • 在执行 finally 语句之前,管制转移语句会将返回值存在本地变量中

看上面这段代码

static int mayThrowException(){  int i = 100;  try {    return i;  }finally {    ++i;  }}public static void main(String[] args) {  System.out.println(FinallyTest.mayThrowException());} 

下面这段代码可能阐明 return i 是先于 ++i 执行的,而且 return i 会把 i 的值暂存,和 finally 一起返回。

finally 的实质

上面咱们来看一段代码

public static void main(String[] args) {  int a1 = 0;  try {    a1 = 1;  }catch (Exception e){    a1 = 2;  }finally {    a1 = 3;  }  System.out.println(a1);} 

这段代码输入的后果是什么呢?答案是 3,为啥呢?

抱着疑难,咱们先来看一下这段代码的字节码

字节码的中文正文我曾经给你标出来了,这里须要留神一下上面的 Exception table,Exception table 是异样表,异样表中每一个条目代表一个异样发生器,异样发生器由 From 指针,To 指针,Target 指针和应该捕捉的异样类型形成。

所以下面这段代码的执行门路有三种

  • 如果 try 语句块中呈现了属于 exception 及其子类的异样,则跳转到 catch 解决
  • 如果 try 语句块中呈现了不属于 exception 及其子类的异样,则跳转到 finally 解决
  • 如果 catch 语句块中新呈现了异样,则跳转到 finally 解决

聊到这里,咱们还没说 finally 的实质到底是什么,仔细观察一下下面的字节码,你会发现其实 finally 会把 a1 = 3 的字节码 iconst_3 和 istore_1 放在 try 块和 catch 块的前面,所以下面这段代码就形同于

public static void main(String[] args) {  int a1 = 0;  try {    a1 = 1;        // finally a1 = 3  }catch (Exception e){    a1 = 2;    // finally a1 = 3  }finally {    a1 = 3;  }  System.out.println(a1);} 

下面中的 Exception table 是只有 Throwable 的子类 exception 和 error 才会执行异样走查的异样表,失常状况下没有 try 块是没有异样表的,上面来验证一下

public static void main(String[] args) {  int a1 = 1;  System.out.println(a1);} 

比方下面咱们应用了一段非常简单的程序来验证,编译后咱们来看一下它的字节码

能够看到,果然没有异样表的存在。

finally 肯定会执行吗

下面咱们探讨的都是 finally 肯定会执行的状况,那么 finally 肯定会被执行吗?恐怕不是。

除了机房断电、机房爆炸、机房进水、机房被雷劈、强制关机、拔电源之外,还有几种状况可能使 finally 不会执行。

  • 调用 System.exit 办法
  • 调用 Runtime.getRuntime().halt(exitStatus) 办法
  • JVM 宕机(搞笑脸)
  • 如果 JVM 在 try 或 catch 块中达到了有限循环(或其余不间断,不终止的语句)
  • 操作系统是否强行终止了 JVM 过程;例如,在 UNIX 上执行 kill -9 pid
  • 如果主机零碎死机;例如电源故障,硬件谬误,操作系统死机等不会执行
  • 如果 finally 块由守护程序线程执行,那么所有非守护线程在 finally 调用之前退出。

finalize 真的没用吗

咱们下面简略介绍了一下 finalize 办法,并阐明了它是一种不好的实际。那么 finalize 调用的机会是什么?为什么说 finalize 没用呢?

咱们晓得,Java 与 C++ 一个显著的区别在于 Java 可能主动治理内存,在 Java 中,因为 GC 的主动回收机制,因此并不能保障 finalize 办法会被及时地执行(垃圾对象的回收机会具备不确定性),也不能保障它们会被执行。

也就是说,finalize 的执行期间不确定,咱们并不能依赖于 finalize 办法帮咱们进行垃圾回收,可能呈现的状况是在咱们耗尽资源之前,gc 却仍未触发,所以举荐应用资源用完即显示开释的形式,比方 close 办法。除此之外,finalize 办法也会生吞异样。

finalize 的工作形式是这样的:一旦垃圾回收器筹备好开释对象占用的存储空间,将会首先调用 finalize 办法,并且在下一次垃圾回收动作产生时,才会真正回收对象占用的内存。垃圾回收只与内存无关

咱们在日常开发中并不提倡应用 finalize 办法,能用 finalize 办法的中央,应用 try...finally 会解决的更好。

你好,我是 cxuan,一枚技术人。我一共写了六本 PDF

《Java 核心技术总结》
《HTTP 外围总结》
《程序员必知的基础知识》
《操作系统外围总结》
《Java 外围根底 2.0》
《Java 面试题总结》

当初我把百度链接给大家放进去了,大家能够点击下方的链接支付

链接: https://pan.baidu.com/s/1mYAeS9hIhdMFh2rF3FDk0A 明码: p9rs