admin管理员组

文章数量:1122879

谈谈final, finally, finalize

final, finally和finalize看起来很相似, 但是工作内容完全不相同.

final关键字

final可以用来修饰在类, 方法和变量上:

  1. 修饰在类上: 该类不可以被集成, 失去扩展性;
  2. 修饰在方法上: 该方法不可被重写(Override);
  3. 修饰在变量上: 变量的值无法被修改;

修饰类

Java语言的设计人员也在设计一些非常基础的类的时候, 也会将这些类使用final来修饰, 我们可以在java.lang包下面发现相当一部分类都是如此, 比如String:

public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence {// other code
}

同样在很多的第三方类库中也是这样, 这样可以有效避免API使用者更改基础功能, 也是保证平台安全的必要手段.

修饰方法

在实际开发的过程中, 我们经常使用final关键字来明确表明我们代码的语义和逻辑结构, 比如在考虑模板方法设计模式的实现过程中, 通常会将顶层模板父类中的模板方法设置为final, 这样实现子类在继承父类之后, 就无法重写一个模板的执行顺序, 从而保证了程序安全.

定义一个玩电脑游戏的模板类如下:

package com.ee2j.spring.design;/*** 玩电脑游戏*/
public abstract class PcGame {public final void playGameTemplate() {// 打开电脑openPc();// 玩具体的游戏playGame();// 关闭电脑if (needClose()) {closePc();}}private void openPc() {System.out.println("打开电脑!");}private void closePc() {System.out.println("关闭电脑!");}protected abstract void playGame();protected boolean needClose() {return Boolean.TRUE;}
}

上述代码中, playGameTemplate()final修饰之后, 子类继承Game之后是无法对这个核心的流程做直接修改的, 只能通过该套模板设计者留下的钩子方法needClose()有限地调整部分流程的执行.

修饰变量

使用final修饰参数或者变量, 也可以清楚地避免意外赋值导致的编程错误, 比如在实际开发过程中, 我们会借助常量(Constant)来进行多个方法之间的通信, 常量的意义就是保证该变量在任何场景下的表达都始终如一, 这就需要借助final的力量了, 比如定义一个标识成功的常量:

public final static String SUCCESS = "success";

在应用开发过程中, 如果尝试对上述代码中的SUCCESS变量做任何重新赋值的操作, 都会出现编译错误, 从而无法改变这个变量当前的值, 从而保证了调用该变量的表现一致, 这样团队中其他成员也无需在意该变量的具体值是什么.

再谈final

通过上述的例子, 我们可以看到final确实带来了某种程度上的不可变效果, 所以可以用于来保护只读数据, 在多线程编程中, 因为明确地不能再赋值final变量, 也可以省去一些额外的同步开销.

但是需要注意的是, final的效果并不是immutable, 比如如下代码:

public final static Double RANDOM = Math.random();

很明显, RANDOM看上去也很符合常量该有的样子, 但是如果在引用开发过程中调用这个"常量", 可能问题就会出的很大, 因为会发现, 每次调用RANDOM之后的结果并不是一样的 !

这是因为final其实约束的只是针对该变量的引用或者赋值, 也就是只能赋值一次, 不能重复赋值, 但是引用的对象本身内部变化, final是没办法管的, 这就要求我们在开发过程中避免为final变量复杂赋值, 务必让常量的值在运行期保持不变.

finally代码块

finally是Java保证重点代码一定要被执行的一种机制, 放在finally代码块中的代码一定会执行, 不管上面的代码执行结果如何(但是有一种特殊情景例外, 下文再论…).

所以我们在实际开发过程中, 一般是通过try-finally或者try-catch-finally形式来进行类似关闭JDBC连接或者Stream的close, 保证unlock等动作, 如下代码所示:

public void close() {FileInputStream fis = null;try {fis = new FileInputStream("xxx.xml");} catch (FileNotFoundException e) {// other code} finally {if (fis != null) {try {fis.close();} catch (IOException e) {// other code}}}
}

这样即使文件没找到, 抛出了异常终止了接下来的业务程序, 我们也可以正常的关闭流.

但是如果你现在选择的Java版本是Java 7以上, 那么更推荐Java 7开始提供的try-with-resource语句, 因为相信Java平台可以更好的处理异常情况, 同事编码量也可以小一些, 何乐而不为呢? 那么, 我可以将上述代码修改如下所示:

try (FileInputStream fis = new FileInputStream("xxx.xml")) {// other code} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}
}

很明显, 可以看到代码量的减少, 而且也没有显式编写关闭流的操作了, 那么流是真的没有关闭么, 其实并不是, 那是因为FileInputStream实现了AutoCloseable接口, 可以从该接口的文档注释中找到如下描述:

/* An object that may hold resources (such as file or socket handles)* until it is closed. The {@link #close()} method of an {@code AutoCloseable}* object is called automatically when exiting a {@code* try}-with-resources block for which the object has been declared in* the resource specification header. This construction ensures prompt* release, avoiding resource exhaustion exceptions and errors that* may otherwise occur....other comments*/

也就是说, 只要是实现了AutoCloseable并在try()中声明的对象, 当try-with-resource代码块执行完的时候,会自动调用close()方法, 所以该步骤是交给Java平台帮我们完成了.

再谈finally

上文说到, 放在finally代码块中的代码一定会执行, 但是会有一种异常情况, 各位看官且观如下代码:

try {System.exit(1);
} finally {System.out.println("come from finally");
}

该段代码的执行结果会是什么? 会输出"come from finally"语句么, 并不会! 因为System.exit(status)用于中断正在运行之中的Java虚拟机, 虚拟机都被干了, 剩下的是啥也无所谓了…当然这是一个极端情况, 正常不会出现这个情况.

finalize方法

finalize()是基础类java.lang.Object的一个方法, 它的作用是保证对象在被垃圾回收之前完成特定资源的回收, 算是个对象生命周期结束之前的"遗言"吧. 但是这个方法已经不推荐使用, 在Java 9中甚至已经被@Deprecated.

为什么这个方法这么不受待见呢 ?

是因为finalize()被设计成在对象被垃圾收集前调用, 这样我们无法保证finalize()什么时候执行, 而且它的执行结果是否能够符合我们的预期.

并且如果finalize()如果不是一个empty的方法, JVM需要对这个对象进行额外的处理, 这样就从本质上成为了快速回收的阻碍, 可能会导致这个对象在多个垃圾收集周期才能被回收, 这样可能会造成等待垃圾收集的对象的堆积, 也是很有可能会造成Out Of Memory的error的 !

本文标签: 谈谈finalfinallyfinalize