一、final 的三种用法
1️⃣如果一个类被声明为 final,意味着它不能再派生出新的子类,即不能被继承,因此它和 abstract 是反义词。想要让一个类永远不被继承,就可以用 final 修饰,但要注意:final 类中所有的成员方法都会隐式的定义为 final 方法。
2️⃣将变量声明为 final,可以保证它们在使用中不被改变,被声明为 final 的变量必须在声明时给定初值,在以后的引用中只能读取不可修改。
- 当 final 修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化。
- 当 final 修饰一个引用类型时,则在初始化之后便不能再让其指向其它对象了,但该引用所指向的对象的内容是可以发生变化的。本质上是一回事,因为引用的值是一个地址,final 要求该值,即地址的值不发生变化。
- final 修饰一个成员变量(属性),必须要显示初始化。这里有两种初始化方式,一种是在变量声明的时候初始化;第二种方法是在声明变量的时候不赋初值,但是要在这个变量所在的类的所有的构造函数中对这个变量赋初值。
- 当函数的参数类型声明为 final 时,说明该参数是只读型的,无法改变该参数的值。
注意:
Java 中,String 被设计成 final,平时使用时,为什么可以改变 String 的值?
字符串常量池是 Java 堆内存中一个特殊的存储区域。当建立一个 String 对象时,假设常量池不存在该字符串,则创建一个,若存在则直接引用已经存在的字符串。当改变 String 对象值的时候,例如String a = "A"; a = "B";
,a 是 String 对象的一个引用(这里所说的 String 对象其实是指字符串常量),当执行 a = "B" 时,并不是原本 String 对象(“A”)发生改变,而是创建了一个新的对象(“B”),令 a 引用它。
3️⃣被声明为 final 的方法也同样只能使用,不能在子类中被重写。使用 final 方法的原因主要有两个:
- 把方法锁定,以防止继承类对其进行更改。
- 效率。在早期Java版本中,会将 final 方法转为内嵌调用。但若方法过于庞大,可能在性能上不会有多大提升。因此在最近版本中,不需要 final 方法进行这些优化了。
注意:若父类中 final 方法的访问权限为 private,将导致子类中不能直接继承该方法。因此,此时可以在子类中定义相同方法名的函数,此时不会与重写 final 矛盾,而是在子类中重新地定义了新方法。
class A{
private final void getName(){}
}
public class B extends A{
public void getName(){}
public static void main(String[]args){
System.out.println("OK");
}
}
二、finally
finally 作为异常处理的一部分,它只能用在 try/catch 语句中,并且附带一个语句块,表示这段语句最终一定会被执行(不管有没有抛出异常,只要 JVM 不关闭,与 finally 对应的 try 语句块得到执行的情况下,都能执行)。经常被用在需要释放外部资源的情况下。
finally 语句块没有执行的情况:
- 在 try 块中,发生异常代码前执行 System.exit(0) 语句,终止 JVM 的运行。
- 发生异常后,在 catch 块中执行 System.exit(0) 语句,终止 JVM 的运行。
- 当一个线程在执行 try 块或者 catch 块时被打断(interrupted)或者被终止(killed),与其相对应的 finally 块可能不会执行。
- 极端情况,就是在线程运行 try 块或者 catch 块时,突然死机或者断电,finally 块肯定不会执行了。
三、finalize
finalize() 是在java.lang.Object里定义的,也就是说每个对象都有该方法。Java 中允许使用 finalize() 在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写 finalize() 可以整理系统资源或者执行其他清理工作。这个方法在 GC 启动,该对象被回收的时候被调用。其实 GC 可以回收大部分的对象(凡是 new 出来的对象,都能搞定,一般情况下又不会用 new 以外的方式去创建对象),所以一般不需要重写 finalize()。特殊情况,当对象被回收的时候释放一些资源,比如:一个 socket 链接,在对象初始化时创建,整个生命周期内有效,那么就需要重写 finalize,关闭这个链接。
使用 finalize 还需要注意一个事,调用 super.finalize();
一个对象的 finalize() 只会被调用一次,而且 finalize() 被调用不意味着 GC 会立即回收该对象。有可能调用 finalize() 后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会调用 finalize(),产生问题。 所以,不推荐使用 finalize(),它跟析构函数不一样。
四、面试
-
int x 被修饰成 final,意味着 x 不能在 addOne() 中被修改。所以编译报错。
判断
@Data
public class Tba {
private int a;
}
public class Wg {
public static void main(String[] args) {
Tba tba = new Tba();
System.out.println(new Wg().addOne(tba));
}
public int addOne(final Tba tba) {
int a = tba.getA();
return ++a;
}
}
答案: 正确。在 addOne() 中,参数 tba 被修饰成 final。如果在 addOne() 里修改了 tba 的引用(tba = new Tba();),那么如同上例这题也是错的。但这里修改的是 tba 的 member vairable(成员变量),而 tba 的 reference 并没有改变。