关于java:看完这篇-finalfinally-和-finalize-和面试官扯皮就没问题了

32次阅读

共计 7714 个字符,预计需要花费 20 分钟才能阅读完成。

我把本人以往的文章汇总成为了 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:()I
istore_0 // 设置 a 的值
invokestatic someValueB:()I
istore_1 // 设置 b 的值
iload_0  // 读取 a 的值
iload_1  // 读取 b 的值
iadd
ireturn 

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

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

// 带 final
static int foo(){

  final int a = 11;
  final int b = 12;

  return a + b;

}

// 不带 final
static 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

正文完
 0