Java经典面试题——谈谈 final、finally、finalize 有什么不同?

典型回答

final 可以用来修饰类、方法、变量,分别有不同的意义,final 修饰的 class 代表不可以继承扩展, final 的变量是不可以修改的,而 final 的方法也是不可以重写的(override)。

finally 则是 Java 保证重点代码一定要被执行的一种机制。我们可以使用 try - finally 或者 try - catch - finally 来进行类似关闭 JDBC 连接、保证 unlock 锁等动作。

finalize 是基础类 java.lang.Object 的一个方法,它的设计目的是保证对象被垃圾收集前完成特定资源的回收。finalize 机制现在已经不推荐使用,并且在 JDK9 开始被标记为 deprecated。

知识扩展

关于 final

final 是一个非访问修饰符,仅适用于变量,方法或类。

final 修饰变量

当使用 final 关键字声明类成员变量或局部变量后,其值不能被再次修改;也经常和 static 关键字一起,作为 类常量 使用。
很多时候会容易把 static 和 final 关键字混淆,static 作用于成员变量用来表示只保存一份副本,而 final 的作用是用来保证变量不可变。
如果 final 变量是引用,这意味着该变量不能重新绑定到引用另一个对象,但是可以更改该引用变量指向的对象的内部状态,即可以从 final 数组或 final 集合中添加或删除元素。最好用全部大写来表示 final 变量,使用下划线来分隔单词。

//一个final成员常量
final int THRESHOLD = 5;
//一个空的final成员常量
final int THRESHOLD;
//一个静态final类常量
static final double PI = 3.141592653589793;
//一个空的静态final类常量
static final double PI;
初始化final变量:

我们必须初始化一个final变量,否则编译器将抛出编译时错误。final 变量只能通过初始化器或赋值语句初始化一次。初始化 final 变量有三种方法:

  • 可以在声明它时初始化 final 变量。这种方法是最常见的。如果在声明时未初始化,则该变量称为空 final 变量。下面是初始化空 final 变量的两种方法。
  • 可以在 instance-initializer 块 或内部构造函数中初始化空的final变量。如果您的类中有多个构造函数,则必须在所有构造函数中初始化它,否则将抛出编译时错误。
  • 可以在静态块内初始化空的 final 静态变量。

这里注意有一个很普遍的误区。很多人会认为 static 修饰的 final常量必须在声明时就进行初始化,否则会报错。但其实则不然,我们可以先使用 static final 关键字声明一个类常量,然后再在静态块内初始化空的 final 静态变量。

何时使用 final 变量 ?

普通变量和 final 变量之间的唯一区别是我们可以将值重新赋值给普通变量;但是对于 final 变量,一旦赋值,我们就不能改变 final 变量的值。因此,final 变量必须仅用于我们希望在整个程序执行期间保持不变的值。

final 修饰类

当使用 final 关键字声明一个类时,它被称为 final 类。被声明为 final 的类不能被扩展(继承)。final 类有两种用途:

  • 一个是彻底防止被继承,因为 final 类不能被扩展。例如,所有包装类如 Integer,Float 等都是 final 类。我们无法扩展它们。
  • final 类的另一个用途是创建一个类似于 String 类的不可变类。只有将一个类定义成为 final 类,才能使其不可变。

Java支持把 class 定义成 final,似乎违背了面向对象编程的基本原则,但在另一方面,封闭的类也保证了该类的所有方法都是固定不变的,不会有子类的覆盖方法需要去动态加载。这给编译器做优化时提供了更多的可能,最好的例子是 String,它就是 final类,Java 编译器就可以把字符串常量(那些包含在双引号中的内容)直接变成 String 对象,同时对运算符 “+” 的操作直接优化成新的常量,因为 final 修饰保证了不会有子类对拼接操作返回不同的值。

final关键字在效率上的作用主要可以总结为以下三点:
  • 缓存:final 配合 static 关键字提高了代码性能,JVM和Java应用都会缓存 final 变量。
  • 同步:final 变量或对象是只读的,可以安全的在多线程环境下进行共享,而不需要额外的同步开销。
  • 内联:使用 final 关键字,JVM会显式地主动对方法、变量及类进行内联优化。

关于 finally

try 关键字最后可以定义 finally 代码块。 finally 块中定义的代码,总是在 try 和任何 catch 块之后、方法完成之前运行。

正常情况下,不管是否抛出或捕获异常 finally 块都会执行。

啥时候 finally 不会被执行 ?

尽管通常编写 finally 代码块是为了这段代码一定被执行到,但是也有一些特殊情况会导致 JVM 不会执行 finally 代码块。

如果操作系统中断了我们的程序,那么 finally 代码块可能就不能被执行。也有很多其他类似的行为导致 finally 代码块不被执行。

调用 System.exit 函数
try {
    System.out.println("Inside try");
    System.exit(1);
} finally {
    System.out.println("Inside finally");
}

结果

 Inside try
调用 halt 函数
try {
    System.out.println("Inside try");
    Runtime.getRuntime().halt(1);
} finally {
    System.out.println("Inside finally");
}

结果

 Inside try
守护线程

如果守护线程刚开始执行到 finally 代码块,此时没有任何其他非守护线程,那么虚拟机将退出,此时 JVM 不会等待守护线程的 finally 代码块执行完成。

Runnable runnable = () -> {
    try {
        System.out.println("Inside try");
    } finally {
        try {
            Thread.sleep(1000);
            System.out.println("Inside finally");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
};
Thread regular = new Thread(runnable);
Thread daemon = new Thread(runnable);
daemon.setDaemon(true);
regular.start();
Thread.sleep(300);
daemon.start();

输出

 Inside try
 Inside try
 Inside finally
try 代码块中无限循环
try {
    System.out.println("Inside try");
    while (true) {
    }
} finally {
    System.out.println("Inside finally");
}

try 代码块出现无限循环,且不出现异常,finally 也将永远得不到执行。

常见陷阱
忽视异常

finally 代码块包含返回语句,没有处理未捕获的异常。

try {
    System.out.println("Inside try");
    throw new RuntimeException();
} finally {
    System.out.println("Inside finally");
    return "from finally";
}

此时,try 代码块中的 RuntimeException 会被忽略,函数返回 "from finally"字符串。

覆盖其他返回语句

如果 finally 代码块中存在返回语句,则 try 和 catch 代码块如果存在返回语句就会被忽略。

try {
    System.out.println("Inside try");
    return "from try";
} finally {
    System.out.println("Inside finally");
    return "from finally";
}

此段代码总是返回 “from finally” 。

改变 throw 或 return 行为

如果再 finally 代码块中扔出异常,则 try 和 catch 中的异常扔出或者返回语句都将被忽略。

try {
    System.out.println("Inside try");
    return "from try";
} finally {
    throw new RuntimeException();
}

这段代码永远都不会有返回值,总是会抛出 RuntimeException。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值