又一次認識java(七) ---- final keyword

你總覺得你會了,事實上你僅僅是隻知其一;不知其二。html

final 關鍵字概覽

final關鍵字可用於聲明屬性、方法、參數和類,分別表示屬性不可變、方法不可覆蓋、參數不可變和類不可以繼承。java

咱們來分別看看它的使用方法。c++

final關鍵字是一個比較簡單的知識點,因此這篇文章我寫的比較舒服,你看着也比較舒服。因爲,很是easy呀~安全

final 屬性

被final修飾的屬性不可變。這樣的不可變的屬性。咱們可以稱之爲「常量」。markdown

這樣的常量大致上有兩種表現形式。多線程

先來看如下的代碼:dom

public class FinalAttribute {
    private final String attribute_a = "chengfan";

    public void test(){
        //attribute_a = "zhangbingxiao"; 不可以這樣寫
    }
}

這是最主要的final屬性,在定義的時候初始化。並且在編譯期值就已經肯定,不能改動。ide

咱們再來看一種:函數

public class FinalAttributeB {
    private final String attribute_b;

    public FinalAttributeB(String attribute_b){
        this.attribute_b = attribute_b;
    }

    public void test(){
        //attribute_b = "zhangbingxiao";
    }

    public void test(String attribute_b){
        //this.attribute_b = attribute_b;
    }
}

這樣的final屬性在編譯期間是沒法肯定屬性值的。僅僅有執行的時候才幹夠肯定(經過構造器初始化屬性)。相同,屬性一經初始化後就不可以改變。因此如下的test方法都沒法改動final屬性。post

上一篇文章中,咱們講了代碼塊,那麼能不能使用代碼塊來初始化final屬性呢?答案固然是可以的:

public class FinalAttributeB {
    private final String attribute_b;

    {
        attribute_b = "zhangbingxiao";
    }

    static {
        //attribute_b = "zhangbingxiao"; 
    }

// public FinalAttributeB(String attribute_b){
// this.attribute_b = attribute_b;
// }

}

經過構造代碼塊初始化final屬性也是可以的,但是這樣就不能再使用構造函數初始化了。因爲構造代碼塊先於構造函數執行。

而final屬性僅僅能且必須初始化一次。

你可能發現了,我寫了靜態代碼塊。但是凝視掉了。沒錯,因爲靜態代碼塊僅僅能初始化靜態屬性。咱們在文章最後再討論它。

這樣的不在定義時初始化,而使用構造函數初始化的,也稱爲空白final變量。它爲final在使用上提供了更大的靈活性。爲此,一個類中的final數據成員就可以實現依對象而有所不一樣,卻有保持其恆定不變的特徵。

那除了構造函數,有沒有別的方式也達到編譯時初始化呢?固然有。比方你使用Random來初始化:

private final int attribute_c = new Random().nextInt();

這樣你僅僅有在執行的時候,才知道屬性值是多少。

剛剛咱們研究的都是基本數據類型。那麼,引用數據類型呢?直接看代碼:

public class FinalAttributeC {
    private final Person person = new Person("zhangbingxiao");

    public void change(){
        person.setName("chengfan");
        System.out.println(person.getName());
    }
   //public void change(Person p){
   //this.person = p;
   //}

    public static void main(String[] args) {
        new FinalAttributeC().change();
    }
}
//結果 : chengfan

凝視掉的代碼是會報錯的代碼,也就是說引用類型person是不可以被改動的。

從結果可以看出來,Person對象內部的屬性被改變了。

因此,對於引用類型來講,引用自己是不可以改變得,但是引用指向的對象是可以改變的。

引用存在於棧中,而對象存在於堆中。引用的值是對象在堆中的地址。

在本質上,final修飾的是引用,而不是對象。因此引用的那個地址不可以變,而和對象沒多大關係。

舉個簡單的樣例,一我的是一個對象,他會穿上衣。褲子,鞋子,這些事人這個對象的屬性。

而人的名字是引用。當你一輩子下來,名字肯定(引用肯定)。你可以隨便換衣服,但是你的名字仍是那個。

我就舉個樣例。別和我擡槓。

。什麼可以去更名字,重名啥的。。你理解了final引用類型這個知識就行了。

final 方法

當一個方法聲明爲final時,該方法不能被不論什麼子類重寫。本類可以重載,但是子類可以使用這種方法。

public class FinalMethod {
    public final void test(){

    }

    public void test(int i){

    }
}

class Test extends FinalMethod{

    //public void test(){} 不可以重寫

    @Override
    public void test(int i) {
        super.test(i);
    }
    public void test(int i,int j) {

    }
}

被final修飾的方法,不可以被重寫。但是不影響本類的重載以及重載函數的重寫。

這裏有一種稱爲內聯(inline)的機制,當調用一個被聲明爲final的方法時。直接將方法主體插入到調用處。而不是進行正常的方法調用(類似於c++的內聯)。這樣有利於提升程序的效率。

但是假設方法過於龐大。可能看不到內聯調用帶來的不論什麼性能提高。在近期的Java版本號中。不需要使用final方法進行這些優化了。

final 參數

當一個方法的形參被final修飾的時候,這個參數在該方法內不可以被改動。

public class FinalParam {
    public void test(final int a ){
        //a = 10; 值不可以被改動
    }
    public void test(final Person p){
        //p = new Person("zhangbingxiao"); 引用自己不可以被改動
        p.setName("zhangbingxiao");  //引用所指向的對象可以被改動
    }
}

對於引用數據類型的改動規則同final屬性同樣。

final修飾參數在內部類中是很是實用的,在匿名內部類中,爲了保持參數的一致性。若所在的方法的形參需要被內部類裏面使用時,該形參必須爲final。

這個知識會在解說內部類的時候進行具體的討論,感興趣的可以先自行研究。

final修飾局部變量

final修飾局部變量時僅僅能初始化(賦值)一次。可以不立刻初始化。

public class StaticPartAttr {
    public void test(){
        final int a ;
        final int b = 2;

        a = 3;
        //a = 4; 報錯 
        //b = 5; 報錯
    }
}

被final修飾的局部變量,僅僅能賦值一次。

你也可以一直不初始化。但是不不賦值,定義這個變量還有什麼用呢?

final 類

被final修飾的類不可以被繼承。所有方法不能被重寫(廢話,都不能繼承了,哪來的重寫)。但是這並不表示類內部的屬性也是不可改動的,除非這個屬性也被final修飾。這點在jdk裏有很是多應用,比方咱們熟知的String,Integer等類都被final修飾。

final類有很是多優勢,譬如它們的對象是僅僅讀的,可以在多線程環境下安全的共享。不用額外的同步開銷等等。

怎樣寫一個不可變類呢?

  • 將類聲明爲final,因此它不能被繼承
  • 將所有的成員聲明爲私有的,這樣就不一樣意直接訪問這些成員
  • 對變量不要提供setter方法
  • 將所有可變的成員聲明爲final,這樣僅僅能對它們賦值一次
  • 經過構造器初始化所有成員,進行深拷貝(deep copy)
  • 在getter方法中,不要直接返回對象自己。而是克隆對象,並返回對象的拷貝

詳情—>丟個連接趕忙跑

值得注意的是。一個類不可以既被abstract修飾又被final修飾。因爲final類不可以被繼承,而abstract類需要被繼承。關於抽象類。咱們會在下篇文章中具體解說。

final 與 static

當final和static同一時候使用的時候,咱們所熟知的「全局常量」就出現了:一個可以處處使用並且不可以改變的屬性,比方咱們熟知的Math.PI。Math.E。

上面咱們說到了靜態代碼塊初始化final變量的問題。

public class FinalStatic {
    private final static double PI = 3.14;
    private final static double E;
    private final static double C ; //這裏會報錯

    static {
        E = 2.71;
    }

    public FinalStatic(double c){
        C = c;
        //PI = C; 這裏會報錯
    }
}

對於靜態final變量,咱們可以直接初始化,或者使用靜態代碼塊。

而不可以使用構造函數或者構造代碼塊。

因爲static要求在編譯期間就肯定值,而後放入靜態區。

而構造函數和構造代碼塊發生在執行期間。因此不存在空白靜態final。

final和private

類中所有的private方法都隱式的指定爲final的,因爲沒法取用private方法,因此也就沒法覆蓋它。可以對private方法加入final修飾符,但並無加入不論什麼額外意義。

總結

關於final的重要知識點

  • final關鍵字可以用於成員變量、本地變量、方法以及類。

  • final成員變量必須在聲明的時候初始化或者在構造器中初始化,不然就會報編譯錯誤。

  • 你不可以對final變量再次賦值。

  • 本地變量必須在聲明時賦值。
  • 在匿名類中所有變量都必須是final變量。
  • final方法不能被重寫。
  • final類不能被繼承。

  • 接口中聲明的所有變量自己是final的。
  • final和abstract這兩個關鍵字是反相關的,final類就不多是abstract的。
  • final方法在編譯階段綁定。稱爲靜態綁定(static binding)。
  • 沒有在聲明時初始化final變量的稱爲空白final變量(blank final variable)。它們必須在構造器中或者代碼塊中初始化。
  • 將類、方法、變量聲明爲final可以提升性能。這樣JVM就有機會進行預計。而後優化。

  • 依照Java代碼慣例,final變量就是常量,並且通常常量名要大寫。

本文內容到此結束。假設文章有錯誤或者你有更好的理解方式,請及時與我聯繫~歡迎指出錯誤,比較我也是個學習的人而不是大神。

轉載請註明出處
本文地址:http://blog.csdn.net/qq_31655965/article/details/54800523

看完了,點個讚唄~

相關文章
相關標籤/搜索