經典案例就是java.lang.String類html
public final class String implements java.io.Serializable, Comparable<String>, CharSequence
還有一些常見的類也是被final所修飾的,以下:基本類型對應的包裝類型(如java.lang.Integer、java.lang.Long等)、字符相關類(java.lang.StringBuilder、java.lang.StringBuffer)、系統類(java.lang.Class、java.lang.System)等。java
是Java語法所規定,如此設計的目的:類不須要被拓展、實現細節不容許改變,估計是爲了安全考慮。安全
另外,不能被重寫或者覆蓋的方法還有:構造方法(也是靜態方法)、靜態方法、私有方法(子類中不可見,因此不容許重寫)ide
一樣是Java語法規定,不容許改變方法的實現。函數
靜態方法爲什麼不能夠被子類重寫?ui
靜態方法的調用是編譯器編譯時靜態綁定的,而實例方法的調用是在運行時動態綁定的;靜態方法沒法像實例方法那樣在運行時動態肯定方法的實現,因此靜態方法的複寫沒有意義。this
另外,由於兩者的調用的方式不一樣,因此子類中不能夠聲明和父類中方法同名的靜態方法;即:靜態方法不能隱藏實例方法,兩者只能存在其一,不然會存在歧義(子類調用方法時,不知該調用哪一個方法)!spa
可是 靜態方法能夠被子類的同名靜態方法隱藏(實際這時,這已是兩個不相關的方法).net
由於調用方式一致,不會像上面形成歧義,雖然父類和子類都定義了一樣的函數,可是編譯器會根據對象的靜態類型激活對應的靜態方法的引用,形成了重寫的假象,實則不是重寫!設計
package com.learn.pra06; public class ClassReference { static class Person { @Override public String toString(){ return "I'm a person."; } public void eat(){ System.out.println("Person eat"); } public void speak(){ System.out.println("Person speak"); } } static class Boy extends Person{ @Override public String toString(){ return "I'm a boy"; } @Override public void speak(){ System.out.println("Boy speak"); } public void fight(){ System.out.println("Boy fight"); } } static class Girl extends Person{ @Override public String toString(){ return "I'm a girl"; } @Override public void speak(){ System.out.println("Girl speak"); } public void sing(){ System.out.println("Girl sing"); } } public static void main(String[] args) { Person boy = new Boy(); Person girl = new Girl(); System.out.println(boy); boy.eat(); boy.speak(); System.out.println(girl); girl.eat(); girl.speak(); } }
首先看看方法表在內存的模型:
經過看Girl和Boy方法表能夠看出繼承的方法從頭至尾開始排列,而且方法引用在子類的中都有固定索引,即都有相同的偏移量;若子類重寫父類某個方法,就會使子類方法表原先存父類的方法引用變成重寫後方法的引用,到這就應該理解爲何能夠根據對象類型而調用到正確的方法,關鍵就在於方法表。
整體流程就是:編譯器將類編譯成class文件,其中方法會根據靜態類型從而將對應的方法引用寫入class中,運行時,JVM會根據INVOKEVIRTUAL 所指向的方法引用在方法區找到該方法的偏移量,再根據this找到引用類型真實指向的對象,訪問這個對象類型的方法表,根據偏移量找出存放目標方法引用的位置,取出這個引用,調用這個引用實際指向的方法,完成多態!
以上均參考 https://blog.csdn.net/dawn_after_dark/article/details/74357049
三、被final修飾的變量不能被「改變」
1)被final修飾的變量不像static那樣,它也能夠修飾局部變量。
2)被final修飾的變量必定要被初始化,不然編譯不經過。
初始化有兩種:直接在變量定義時初始化 和 在構造函數中初始化(每一個構造函數都要初始化即每一個實例化對象的入口都要進行初始化)。
3)被final修飾的基本類型變量,它的值是不可變的。被final修飾的引用類型變量,它的引用地址是不可變的,對象裏的內容是可變的。
四、在匿名內部類中使用外部方法的局部變量(也多是函數的參數形式)時,該變量必須被final修飾。
1)緣由:匿名內部類裏面使用外部方法中的局部變量時,其實就是內部類的對象在使用它,內部類對象生命週期中均可能調用它,而內部類對象試圖訪問外部方法中的局部變量時,外部方法的局部變量極可能已經不存在了(方法執行完,局部變量便從棧中彈出,生命週期結束不復存在了),那麼就得延續其生命,拷貝到內部類中,而拷貝會帶來不一致性,從而須要使用final聲明保證一致性。
==》爲了解決內部類實例與外部方法局部變量生命週期不一樣的問題,匿名內部類備份了變量,爲了解決備份變量引發的修改後沒有同步修改的問題,外部變量須要被定義成final ==》 匿名內部類使用final不是怕修改,是怕不能同步修改。
2)匿名內部類使用外部類的成員變量是不須要是final的。 由於內部類自己都會含有一個外圍了的引用(外圍類.this),因此回調的時候必定能夠訪問到
private Animator createAnimatorView(final View view, final int position) { MyAnimator animator = new MyAnimator(); animator.addListener(new AnimatorListener() { @Override public void onAnimationEnd(Animator arg0) { Log.d(TAG, "position=" + position); } }); return animator; }
內部類回調裏訪問position的時候createAnimatorView()早就執行完了,position若是不是final的,回調的時候確定就沒法拿到它的值了,由於局部變量在函數執行完了之後就被回收了。
以上參考 https://www.cnblogs.com/DarrenChan/p/5738957.html
public interface MyInterface { void doSomething(); } public class TryUsingAnonymousClass { public void useMyInterface() { final Integer number = 123; System.out.println(number); MyInterface myInterface = new MyInterface() { @Override public void doSomething() { System.out.println(number); } }; myInterface.doSomething(); System.out.println(number); } }
咱們進行反編譯,結果是:
class TryUsingAnonymousClass$1 implements MyInterface { private final TryUsingAnonymousClass this$0; private final Integer paramInteger; TryUsingAnonymousClass$1(TryUsingAnonymousClass this$0, Integer paramInteger) { this.this$0 = this$0; this.paramInteger = paramInteger; } public void doSomething() { System.out.println(this.paramInteger); } }
能夠看到:外部類的實例引用 this$0 和外部方法局部變量 number 都做爲構造方法的參數傳入了匿名內部類。
五、final變量與普通變量有什麼區別,何時能夠相等?
public class FinalTest2 { public static void main(String[] args) { final String str1 = "test"; final String str2 = getContent(); String str3 = "test"; String str4 = str1 + ""; String str5 = str2 + ""; System.out.println(str3 == str4); System.out.println(str3 == str5); } public static String getContent(){ return "test"; } }
輸出後的結果爲true和false。這是爲何呢?
若是是final修飾直接定義的字符串或者是基本類型,它在編譯期間就會肯定其值,則編譯器會把它當作常量,放在常量池中。因此當有使用到它的地方會直接用常量替換(str1 str3 str4都是常量池中的同一個常量)。而其餘都是運行時纔會肯定的值,因此依然使用變量去計算。在代碼中str2變量,雖然用是final修飾可是它的值要在的運行時才能肯定,因此它至關於普通變量。而str5的生成,由於str2是普通變量,因此str5會經過StringBulider去計算整個表達式的值,返回一個新的String,引用地址變了。因此第12行的輸出爲false;
六、final與finally 和finalize的區別
finally是異常處理語句結構的一部分,表示最終執行。
finalize是Object類的一個析構方法,在垃圾收集器執行的時候會調用被回收對象的此方法,供垃圾收集時的其餘資源回收,例如關閉文件等。
當垃圾回收器將要釋放無用對象的內存時,先調用該對象的finalize()方法。若是在程序終止前垃圾回收器始終沒有執行垃圾回收操做,那麼垃圾回收器將始終不會調用無用對象的finalize()方法。在Java的Object基類中提供了protected類型的finalize()方法,所以任何Java類均可以覆蓋finalize()方法,一般,在析構方法中進行釋放對象佔用的相關資源的操做。
Java 虛擬機的垃圾回收操做對程序徹底是透明的,所以程序沒法預料某個無用對象的finalize()方法什麼時候被調用。若是一個程序只佔用少許內存,沒有形成嚴重的內存需求,垃圾回收器可能沒有釋放那些無用對象佔用的內存,所以這些對象的finalize()方法尚未被調用,程序就終止了。
程序即便顯式調用System.gc()或Runtime.gc()方法,也不能保證垃圾回收操做必定執行,也就不能保證對象的finalize()方法必定被調用。
當垃圾回收器在執行finalize()方法的時候,若是出現了異常,垃圾回收器不會報告異常,程序繼續正常運行。
參考:https://www.cnblogs.com/yuanfy008/p/8021673.html