Java中“final、finally、finalize”三者的区别
1. final
在java中,final关键字是修饰符,可以用来修饰类、方法和变量(成员变量或局部变量)。
(1)修饰类
当用final修饰一个类时,表明这个类不能被继承。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。
在使用final修饰类的时候,要注意谨慎选择,除非这个类真的在以后不会用来继承或者出于安全的考虑,尽量不要将类设计为final类。
(2)修饰方法
使用final方法的原因主要有两个:
(1) 把方法锁定,以防止继承类对其进行更改。
(2) 效率问题,在早期的java版本中,会将final方法转为内嵌调用。但若方法过于庞大,可能在性能上不会有多大提升。因此在最近版本中,不需要final方法进行这些优化了。
因此,如果想明确禁止某方法在子类中被覆盖的情况下才将父类中该方法设置为final。即父类的final方法是不能被子类所覆盖的,也就是说子类是不能够存在和父类一模一样的方法的。
final方法意味着“最后的、最终的”含义,即此方法不能被重写。
注意: 若父类中final方法的访问权限为private,将导致子类中不能直接继承该方法。因此,此时可以在子类中定义相同方法名的函数,此时不会发生与重写final的矛盾,而是在子类中重新地定义了新方法。
(3)修饰变量
final成员变量表示常量,只能被赋值一次,赋值后值不能进行改变。
当final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化;
如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。
本质上是一回事,因为引用的值是一个地址,final要求值不发生变化,即地址的值不发生变化。
final修饰一个成员变量(属性),必须要初始化,这里有两种初始化方式:
一种是在变量声明的时候初始化;
第二种方法是在声明变量的时候不初始化,但是要在这个变量所在的类的所有的构造函数中对这个变量初始化。
当函数的参数类型声明为final时,说明该参数是只读型的。即你可以读取使用该参数,但是无法改变该参数的值。
上面代码中分别在两构造函数中对常量 a 进行赋值;对于对象 obj 的重新指向新的地址就报错了。
2. finally
finally作为异常处理的一部分,它只能用在try/catch语句中,并且附带一个语句块,表示这段语句最终一定会被执行(不管有没有抛出异常),经常被用在需要释放资源的情况下。(这句话其实存在一定的问题)
很多人都认为finally语句块一定会执行,其实不然,下面我列出了几个例子:
public class FinalFather {
public static void main(String[] args) {
System.out.println(finanalyTest());
}
public static int finanalyTest(){
if (true) {
return 0;
}
System.out.println("***************");
try {
result = 2/0;
} catch (Exception e) {
e.printStackTrace();
}finally {
System.out.println("执行finally模块!!!");
}
return 1;
}
}
结果:
public class FinalFather {
public static void main(String[] args) {
System.out.println(finanalyTest());
}
public static int finanalyTest(){
// if (true) {
// return 0;
// }
System.out.println("***************");
int result = 2/0;
try {
result = 3/0;
} catch (Exception e) {
e.printStackTrace();
}finally {
System.out.println("执行finally模块!!!");
}
return 1;
}
}
结果:
以上两种情况都没用执行 finally 语句,因为try/catch模块没有执行,所以只有与finally对应的try语句块得到执行的情况下,finally语句块才会执行。
以上两种情况在执行try语句块之前已经返回或抛出异常,所以try对应的finally语句并没有执行。
但是,在某些情况下,即使try语句执行了,finally语句也不一定执行。例如以下情况:
public class FinalFather {
public static void main(String[] args) {
System.out.println(finanalyTest());
}
public static int finanalyTest(){
// if (true) {
// return 0;
// }
System.out.println("***************");
// int result = 2/0;
try {
int result = 3/0;
} catch (Exception e) {
e.printStackTrace();
System.exit(0);
}finally {
System.out.println("执行finally模块!!!");
}
return 1;
}
}
结果:
finally 语句块还是没有执行,为什么呢?因为我们在 try 语句块中执行了 System.exit (0) 语句,终止了 Java 虚拟机的运行。
例题:
public class FinalFather {
public static void main(String[] args) {
System.out.println(finanalyTest());
}
public static int finanalyTest(){
int count = 0;
try {
int result = 3/0;
return ++count; // 这里 count 没有执行
} catch (Exception e) {
e.printStackTrace();
return ++count; // 1
}finally {
System.out.println("执行finally模块!!!");
return ++count; // 2
}
}
}
结果:
首先finally语句在该代码中一定会执行,从运行结果来看,finally语句最后返回 2,仿佛其他return语句被屏蔽掉了。
事实也确实如此,因为finally用法特殊,所以会撤销之前的return语句,继续执行最后的finally块中的代码。
3. finalize
finalize()是在java.lang.Object里定义的,即:每一个对象都有这个方法。这个方法由 System.gc() 启动,该对象被回收的时候被调用。其实 gc 可以回收大部分的对象(凡是new出来的对象,gc都能进行回收),所以一般是不需要程序员去实现finalize 的。 这也正是 Java 语言是健壮的体现。
在一些特殊情况下,需要程序员实现 finalize,当对象被回收的时候释放一些资源,比如:一个socket链接,在对象初始化时创建,整个生命周期内有效,那么就需要实现finalize,关闭这个链接。
使用 finalize 还需要注意一个事,调用 super.finalize();
一个对象的finalize()方法只会被调用一次,而且finalize()被调用不意味着gc会立即回收该对象,所以有可能调用finalize()后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会调用finalize(),产生问题。 所以,推荐不要使用finalize()方法。
public class ObjectGc {
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("垃圾回收............");
}
}
public class SystemDemo {
public static void main(String[] args) {
ObjectGc objectGc1 = new ObjectGc();
objectGc1 = null;
ObjectGc objectGc2 = new ObjectGc();
objectGc2 = null;
ObjectGc objectGc3 = new ObjectGc();
objectGc3 = null;
ObjectGc objectGc4 = new ObjectGc();
objectGc4 = null;
System.gc();
for (int i = 0; i < 10; i++) {
System.out.println(i);
if (i == 5) {
System.exit(0);
}
}
}
}
结果: