看完这篇 finalfinally 和 finalize 和面试官扯皮就没问题了

Posted 程序员cxuan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了看完这篇 finalfinally 和 finalize 和面试官扯皮就没问题了相关的知识,希望对你有一定的参考价值。

我把自己以往的文章汇总成为了 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 能否提高性能一直是业界争论的点,很多

以上是关于看完这篇 finalfinally 和 finalize 和面试官扯皮就没问题了的主要内容,如果未能解决你的问题,请参考以下文章

看完这篇彻底了解Nginx

看完这篇,可以随意玩转Nginx

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

RTSP 协议分析——看完这篇直接毕业

看完这篇Gradle,你才能去面腾讯

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