從jdk1.0到今天,JAVA技術經過十餘年的發展,技術上已經發生了巨大的變化.但final變數的定義從它
誕生那天起,就沒有發生任何變化,也就是這十多年它就一直表示它原來的意思.
但遺憾的是,經過十多年仍然有90%的人沒有理解它的真實含義,也沒有一篇文章,包括我所見到的所有介紹
JAVA的書籍(包括TKJ)都沒有說清楚,我相信肯定有些作者是理解的,但沒有一個作者向讀者說清楚.而中國網友
大多數人被一篇胡說八道的<<淺談Java中final,finalized,finally>>的文章跑過馬.(臉紅一下:當初我也是).
final變數的定義本身並不複雜,就是變數一經初始化就不能再指向其它對象.在c++中它是一個const指標,而
不是指向const變數的指標,const指標的意思是說它只能一直指向初始化時的那個地址.但那個地址中對象本身
是可以修改的.而指向const變數的指標是說所指對象本身是不能修改的.
如:final StringBuffer sb = new StringBuffer("Axman");
sb = new StringBuffer("Sager");//錯誤,sb不能再指向其它對象.
sb.append(" was changed!"); //sb指向的地象本身可以修改.
先說final變數初始化:
很多文章都這麼說:其初始化可以在兩個地方,一是其定義處,二是在建構函式中,兩者只能選其一。
胡說八道!
final變數可以在任何可以被始化的地方被始化,但只能被初始化一次.一旦被初始化後就不能再次賦
值(重新指向其它對象),作為成員變數一定要顯式初始化,而作為臨時變數則可以只定義不初始化(當然也不能引用)
即使是作為一個類中的成員變數,也還可以在初始化塊中初始化,所以"其初始化可以在兩個地方,一是其定義處,
二是在建構函式中,兩者只能選其一"是錯誤的.
作為成員變數時,final欄位可以設計不變類,是不變類的一個必要條件但不是一個充要條件.至少可以保證欄位不
會以setXXX()這樣的方式來改變.但無法保證欄位本身不被修改(除非欄位本身也是不變類);
對於方法參數的final變數:
對於方法參數的變數定義為final,90%以上的文章都說"當你在方法中不需要改變作為參數的物件變數時,明確使
用final進行聲明,會防止你無意的修改而影響到調用方法外的變數。"
胡說八道!
我不知道這個修改是說重新賦值還是修改對象本身,但無論是哪種情況,上面的說法都是錯誤的.
如果是說重新賦值,那麼:
Java代碼
- public static void test(int[] x){
- x = new int[]{1,2,3};
- }
-
- int[] out = new int[]{4,5,6};
- test(out);
- System.out.println(out[0]);
- System.out.println(out[1]);
- System.out.println(out[2]);
public static void test(int[] x){ x = new int[]{1,2,3}; } int[] out = new int[]{4,5,6}; test(out); System.out.println(out[0]); System.out.println(out[1]); System.out.println(out[2]);
調用test(out);無論如何也不會影響到外面變數out.你加不加final根本沒有意義.final只會強迫方法內
多聲明一個變數名而已,即把x = new int[]{1,2,3};改成int y = new int[]{1,2,3}; 其它沒有任何實際意義.
如果說是修改對象本身:
Java代碼
- public static void test(final int[] x){
- x[0] = 100;
- }
- int[] out = new int[]{4,5,6};
- test(out);
- System.out.println(out[0]);
public static void test(final int[] x){ x[0] = 100; } int[] out = new int[]{4,5,6}; test(out); System.out.println(out[0]);
難道你用final修飾就不可以修改了?所以說對於方法參數中final是為了不影響調用方法外的變數那是胡說八道的.
那我們到底為什麼要對參數加上final?其實對方法參數加final和方法內變數加上final的作用是相同的,即為了將它們
傳給內部類回調方法:
Java代碼
- abstract class ABSClass{
- public abstract void m();
- }
abstract class ABSClass{ public abstract void m();}
現在我們來看,如果我要實現一個在一個方法中匿名調用ABSClass.應該:
Java代碼
- public static void test(String s){
- //或String s = "";
- ABSClass c = new ABSClass(){
- public void m(){
- int x = s.hashCode();
-
- System.out.println(x);
-
- }
- };
- //其它代碼.
- }
public static void test(String s){ //或String s = ""; ABSClass c = new ABSClass(){ public void m(){ int x = s.hashCode(); System.out.println(x); } }; //其它代碼. }
注意這裡,一般而言,回調方法基本上是在其它線程中調用的.如果我們在上面的
Java代碼
- ABSClass c = new ABSClass(){
- public void m(){
- int x = s.hashCode();
-
- System.out.println(x);
-
- }
- };
ABSClass c = new ABSClass(){ public void m(){ int x = s.hashCode(); System.out.println(x); } };
後面直接調用c.m();應該是沒有意義的.但這不重要,重要的是只要有可能是在其它線程中調用,那我們就必須
為s儲存引用控制代碼.
我們先來看GC工作原理,JVM中每個進程都會有多個根,每個static變數,方法參數,局部變數,當然這都是指參考型別.
基礎類型是不能作為根的,根其實就是一個儲存地址.
GC在工作時先從根開始遍曆它引用的對象並標記它們,如此遞迴到最末梢,所有根都遍曆後,沒有被標記到的對象說明沒
有被引用,那麼就是可以被回收的對象(有些對象有finalized方法,雖然沒有引用,但JVM中有一個專門的隊列引用它
們直到finalized方法被執行後才從該隊列中移除成為真正沒有引用的對象,可以回收,這個與本主題討論的無關,包括
代的劃分等以後再說明).這看起來很好.
但是在內部類的回調方法中,s既不可能是靜態變數,也不是方法中的臨時變數,也不是方法參數,它不可能作為根,在內部類
中也沒有變數引用它,它的根在內部類外部的那個方法中,如果這時外面變數重指向其它對象,則這個對象就失去了引用,
可能被回收,而由於內部類回調方法大多數在其它線程中執行,可能還要在回收後還會繼續訪問它.這將是什麼結果?
而使用final修飾符不僅會保持對象不會改變,而且編譯器還會持續維護這個對象在回調方法中的生命週期.所以這才是final
變數和final參數的根本意義.