Java中關鍵字final的用法

final作爲java的關鍵字,它可以修飾類,方法,變量及參數,但是有很多朋友可能不太清楚它怎麼用,什麼時候用,有什麼優點,下面我們來一一解釋。

當final修飾一個類時,表明其爲最終類,它不能被繼承,並且類中所有的屬性和方法都默認是final類型,如String,Integer等包裝類均爲final類。

方法

被final修飾的方法不可被重寫。它可以防止任何繼承類修改方法的意義和實現,而且,使用final修飾方法的執行效率一般高於普通方法,這裏不得不提一下Java的內聯機制。

當我們調用方法時,實際上是將程序的執行轉移到該方法所在的內存地址上,將該方法執行完後,再返回到執行該方法前的位置,這種轉移操作要求在轉移前保存當前的數據以及內存地址,在執行完後再恢復現場,繼續按照轉移前的地址執行,也就是通常所說的壓棧和出棧(這段文字有點繞口,簡單來說,比如A方法在執行到第10行的時候調用了B方法,JVM會先保存A方法當前數據和執行地址,然後跳轉到B方法所在的內存地址執行B方法,執行完後,再返回A方法第十行繼續執行),因此,函數調用有一定時間和空間方面的開銷,對於函數體積不大,但是頻繁調用的函數來說,這個開銷就會放大。

因此,對於這種函數體積不大又頻繁調用的的方法,我們可以通過內聯函數來提升運行效率,當我們對一個方法使用final修飾時,這個方法就有可能成爲內聯函數(JVM會根據方法的執行效率決定是否內聯)。

看下面代碼,解釋函數內聯前後的區別。

內聯前:

class A {

    int value;

    public final int get(){

        return value;

    }

}

public class B {

    public void sum() {

        A a = new A();

        //調用a的get方法

        int x = a.get();

    }

}

內聯後:

class A {

    int value;

    public final int get(){

        return value;

    }

}

public class B {

    public void sum() {

        A a = new A();

        //此處將get方法展開爲內聯函數

        int x = a.value;

    }

}

變量

使用final修飾的變量稱爲常量(大寫字母表示),只能被賦值一次,且賦值之後無法改變,這裏的變量又可以分爲基本類型變量和引用類型變量,final修飾基本類型變量時,變量的值不可改變;修飾引用變量時,變量指向的對象地址不可改變。

這裏還涉及到了一個類似C語言的宏替換概念,由於final修飾的String變量不可更改,所以,當一個String變量被final修飾時,這個值在編譯期就可以確定,所有將該變量直接替換爲它對應的值,如下:

public class test {

    public static void main(String[] args) {

        final String a = "hello";

        String b = "hello";

        final String c = "world";

        String d = "hello" + "world";

        String e = a + c;

        String f = b + c;

        String g = "helloworld";

        System.out.println(g == d);//true

        System.out.println(g == e);//true

        System.out.println(g == f);//false

    }

}

在編譯期,由於a和c的值已經確定並且不會再更改(效果同d),所以e的值能夠在編譯期就確定下來,直接指向了常量區的g,前兩個均爲true;再看下f,由於b值的不確定性,所以在編譯期不能確定其值,只能在運行時確認,所以(g == f)爲false。

參數

final修飾的參數有一個只讀的屬性,即可以讀取該參數,但是無法更改參數的值,同修飾變量一樣,當參數爲基本類型時,該參數的值不可改變;當參數爲引用類型時,參數的引用地址不可改變。

final的內存語義

在《【併發編程】揭開volatile的神祕面紗》中,說到了volatile可以禁止指令重排序,final同樣有這樣的作用,對於final域,編譯器和處理器要遵守兩個重排序規則。

在構造函數內對一個final域的寫入,與隨後把這個被構造對象的引用賦值給一個引用變量,這兩個操作之間不能重排序。

意思是說,在對象引用爲任意線程可見之前,對象的final域已經被正確初始化了(JVM禁止把final域的寫重排序到構造函數之外,要保證該效果,還要確保final引用沒有從構造函數溢出)。

初次讀一個包含final域的對象的引用,與隨後初次讀這個final域,這兩個操作之間不能重排序。

意思是說,在讀一個對象的final域之前,一定會先讀包含這個final域的對象的引用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章