“横看成岭侧成峰,远近高低各不同。不识庐山真面目,只缘身在此山中。”
这首诗来自于宋朝苏轼《题西林壁》,它的意思是,庐山从正面看,它是一道道连绵起伏的山岭;从侧面看,它是一座巍然耸立的险峰,而从远处、近处、高处、低处看,庐山又呈现各种不同的样子。
我们之所以认不清庐山真正的面目,是因为人身处在庐山之中。诗的意思是指同一个事物在不同的角度和不同的时间看是不一样的,指出我们在看待或者观察问题时应当客观全面,如果主观片面的看问题,就会得出不正确的结论。
final、finally、finalize这三个关键字长得很像,对于Android初学者或者Java初学者来说容易混淆,有时分不清楚该怎样正确的使用它们,并且它们也经常是某些公司面试基础知识中的重点,所以我们有必要来专项学习一下它。今天我就来总结一下它们的定义,使用场景,区别等,争取把它们所有相关的知识点都一次性讲明白,使大家都能够很好的掌握它们。
1、关于final
final是Java中的一个修饰关键字,它可以用来修改类,方法和变量。
(1)、如果用它来修饰类,即类被声明为final,意味着这个类不能再派生出新的子类,不能作为父类被继承。例如,String、StringBuilder、StringBuffer、Math等类。它和abstract(抽象)是互斥的,用abstract修饰的类是必须要继承才能实例化对象的,因此一个类不能既被声明为 abstract,又被声明为final。
(2)、而用final修饰变量或方法时,可以保证变量或方法在使用中不被改变。
被final修饰的变量必须在声明时给定初值,而在以后的使用中只能读取,不可修改,场景包括形参,局部变量,成员变量等情况。如果修饰的是基本数据类型,则初始化之后变量的值不能被改变;如果修饰的变量是引用类型,那么它只能指向初始化时指向的那个对象,不能再指向别的对象,但是对象当中的内容是允许改变的。
(3)、被final修饰的方法也同样只能使用,不能被重载,但是子类可以使用或调用父类中final修饰的方法。
(4)、final有一个特殊的使用场景。
当方法内部声明的类或者方法内定义的匿名内部类,访问该方法内定义的局部变量时,该变量必须要用final修饰。因为当内部类或内部匿名类访问方法的局部变量时,相当于扩大了局部变量的作用域,如果局部变量不用 final 修饰,我们就可以在内部类中随意修改该局部变量值,而在该局部变量的作用域范围之外是可以看到这些修改后的值,这样将会出现安全问题,所以必须用final修饰,不允许内部类或内部匿名类修改变量的值。而方法内部类或内部匿名类在访问该方法所在的类的成员变量时,却可以不用final修饰。
如下所示,
public class TestFinal {
String str1 = "no need final";
//匿名内部类
public void testAnonyClass() {
final String str2 = "need final";
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(str1);
System.out.println(str2);
}
}).start();
}
public void testInnerClass(){
final String str3 = "also need final";
//方法内部定义的内部类
class innerClass{
public void method1(){
System.out.println(str1);
System.out.println(str3);
}
}
}
}
如上所示str2在方法testAnonyClass中的匿名类中访问,它需要声明为final类型才可以,str3在方法testInnerClass中的内部类innerClass中访问,也同样需要声明为final。这两个方法中的内部类或内部匿名类在访问方法所在的类中的成员变量str1时,str1不需要声明为final。
2、关于finally
finally也是Java中的一个保留字,用在异常处理时,提供 finally代码块来执行最后的清除操作。
我们在写一段可能抛出异常的代码段时,如果我们希望捕获这个代码段中抛出的异常,那我们一般这样来写,
try {
//调用方法,期间可能抛出异常
//....
} catch (Exception e) {
e.printStackTrace();
} finally {
//可以进行一些清理
//....
}
如果抛出一个异常,那么相匹配的 catch 子句就会执行,然后最终控制会进入 finally代码块。
finally语句不会被执行的唯一情况是:先执行了用于终止程序的System.exit()方法,这个时候不会走到finally语句中,或者如果在执行过程中突然断电了,这时所有进程都会终止,也不会执行finally语句。
大家看一下如下demo,猜一猜打印的结果是什么?
public class TestFinally{
public int testFinally(){
try{
System.out.println("-----try-----");
return 1;
}finally{
System.out.println("-----finally-----");
return 2 ;
}
}
public static void main(String[]args){
TestFinally te= new TestFinally();
int t = te.testFinally();
System.out.println(t);
}
}
这里有两个return,按正常逻辑在try中调用return后,方法就返回了,但是因为下面有finally语句,并且其中也有一个return,所以最终应该返回finally语句中return对应的结果。
以上程序运行结果如下所示,
-----try-----
-----finally-----
2
如果注释掉finally语句中return,返回结果如下所示,
-----try-----
-----finally-----
1
可见finally中的语句永远都是要执行的。
3、关于finalize
finalize是Java中的一个方法名,它在Object类中定义,因此所有的类都继承了它。finalize() 方法的作用是在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。垃圾收集器在确定这个对象没有被引用时将调用此对象的finalize()方法,或者说finalize() 方法是在垃圾收集器删除对象之前对这个对象调用的。子类可以覆盖finalize() 方法从而整理自己定义的各类资源,或者执行资源清理工作。
使用场景,当销毁一个对象时,需要执行一些额外操作。例如,如果一个对象创建了一些非Java 资源,如文件句柄,window 字符字体或者数据库cursor等,这时你在这个对象被销毁前,要保证这些资源被安全释放。那如何保证呢?Java提供了一种收尾(finalization)机制,可以通过重写finalize() 方法来实现这种机制,在这种机制或者finalize() 方法中,可以将对象在进行垃圾回收前释放之前创建的其它资源。
在finalize ( )方法中,你要指定这个对象被撤消前必须执行的操作。系统的垃圾回收会周期性地运行,将由JVM的垃圾回收机制来控制,它会检查此对象是不是不再被运行状态引用或间接地引用。如果没有被引用,将会释放此对象,对应的inalize( ) 方法就会执行。
finalize()方法定义如下所示:
protected void finalize( )
{
// finalization code here
}
但是,finalize( )并不确保一个对象超出了它的作用域时就会执行,也就是说你不可能知道何时,甚至是否finalize( ) 被调用,因为这完全取决于系统的垃圾回收机制。所以,释放对象所创建的资源最好使用其它方法来实现,而不能仅仅依靠finalize( ) 来完成程序的资源释放工作。
finalize()在什么场景使用呢?
有三种情况
(1)、对象没有了直接和间接引用,垃圾回收器(garbage colector)决定回收某对象时,比如运行System.gc()的时候。
(2)、程序退出时为对象调用一次finalize方法。
(3)、显式的调用finalize方法。`
4、总结
由以上介绍,我们应该对final、finally、finalize有所了解了。对于它们的使用,还需要我们在工作中多摸索和多实践,这样才会理解的更加深刻,运用起来才会得心应手,才不会对它们产生混淆。
本公众号将以推送Android各种技术干货或碎片化知识,以及整理老司机日常工作中踩过的坑涉及到的经验知识为主,也会不定期将正在学习使用的新技术总结出来进行分享。每天一点干货小知识把你的碎片时间充分利用起来。