1. 程式人生 > 其它 >Final 關鍵字的用法

Final 關鍵字的用法

Final 關鍵字的用法

本篇部落格示例來自來自 https://github.com/BruceEckel/OnJava8-examples

且大多數內容引用 https://lingcoder.github.io/OnJava8/#/sidebar

一、final 在 Java中的用法

1、final類

final類不能被繼承,因此final類的成員方法沒有機會被覆蓋,預設都是final的。在設計類時候,如果這個類不需要有子類,類的實現細節不允許改變,並且確信這個類不會再被擴充套件,那麼就設計為final類。 final方法不能被子類的方法覆蓋,但可以被繼承。

2、final方法

如果一個類不允許其子類覆蓋某個方法,則可以把這個方法宣告為final方法。

使用final方法的原因有二:

第一、把方法鎖定,防止任何繼承類修改它的意義和實現。

第二、高效。編譯器在遇到呼叫final方法時候會轉入內嵌機制,大大提高執行效率。

3、final變數(常量)

用final修飾的成員變量表示常量,只能被賦值一次,賦值後值無法改變!

final修飾的變數有三種:靜態變數、例項變數和區域性變數,分別表示三種類型的常量。

另外,final變數定義的時候,可以先宣告,而不給初值,這種變數也稱為final空白,無論什麼情況,編譯器都確保空白final在使用之前必須被初始化。但是,final空白在final關鍵字final的使用上提供了更大的靈活性,為此,一個類中的final資料成員就可以實現依物件而有所不同,卻有保持其恆定不變的特徵。

4、final引數

當函式引數為final型別時,你可以讀取使用該引數,但是無法改變該引數的值。

final不能用於修飾構造方法。

注:父類的private成員方法是不能被子類方法覆蓋的,因此private型別的方法預設是final型別的。

二、final 資料

下面例子展示了 final 屬性的使用:

class Value {
    int i; // package access
    
    Value(int i) {
        this.i = i;
    }
}

public class FinalData {
    private static Random rand = new Random(47);
    private String id;
    
    public FinalData(String id) {
        this.id = id;
    }
    // 帶有編譯時值的final基本型別,它們都可用作編譯時常量
    private final int valueOne = 9;
    private static final int VALUE_TWO = 99;
    // public意味著可以在包外訪問,static強調只有一個,final說明是一個常量。
    public static final int VALUE_THREE = 39;
    // 不是編譯時常量,因為i4 和 INT_5 都是在執行時才能被賦值
    //不同物件的 i4 值可能是不同的
    private final int i4 = rand.nextInt(20);
    //不同物件的 INT_5 值是同一個
    static final int INT_5 = rand.nextInt(20);
    private Value v1 = new Value(11);
    //對於被final修飾的引用,引用指向的物件不能進行修改,但是物件的屬性或者值可以進行修改
    private final Value v2 = new Value(22);
    private static final Value VAL_3 = new Value(33);
    // 陣列也是物件,同理如上
    private final int[] a = {1, 2, 3, 4, 5, 6};
    
    @Override
    public String toString() {
        return id + ": " + "i4 = " + i4 + ", INT_5 = " + INT_5;
    }
    
    public static void main(String[] args) {
        FinalData fd1 = new FinalData("fd1");
        //- fd1.valueOne++; // 不能修改被final修飾的基本資料型別
        fd1.v2.i++; // 可以修改被final修飾的引用指向的物件的屬性
        fd1.v1 = new Value(9); // 可以修改引用指向的物件,因為沒有被final修飾
        for (int i = 0; i < fd1.a.length; i++) {
            fd1.a[i]++; // 原理如 fd1.v2.i++
        }
        //- fd1.v2 = new Value(0); // 不能修改v2引用指向的物件
        //- fd1.VAL_3 = new Value(1); // 如上
        //- fd1.a = new int[3];	// 如上
        System.out.println(fd1);
        System.out.println("Creating new FinalData");
        FinalData fd2 = new FinalData("fd2");
        System.out.println(fd1);
        System.out.println(fd2);
    }
}

執行結果:

fd1: i4 = 15, INT_5 = 18
Creating new FinalData
fd1: i4 = 15, INT_5 = 18
fd2: i4 = 13, INT_5 = 18

對於基本型別,final 使數值恆定不變,而對於物件引用,final 使引用恆定不變。一旦引用被初始化指向了某個物件,它就不能改為指向其他物件。但是,物件本身是可以修改的,Java 沒有提供將任意物件設為常量的方法。(你可以自己編寫類達到使物件恆定不變的效果)這一限制同樣適用陣列,陣列也是物件。

三、空final

空白 final 指的是沒有初始化值的 final 屬性。編譯器確保空白 final 在使用前必須被初始化。這樣既能使一個類的每個物件的 final 屬性值不同,也能保持它的不變性。

class Poppet {
    private int i;
    
    Poppet(int ii) {
        i = ii;
    }
}

public class BlankFinal {
    private final int i = 0; // 正常定義的final常量
    private final int j; // 空final
    private final Poppet p; // Blank final reference
    // 空final必須在構造方法中進行初始化,如果未進行初始化,則編譯器會報錯
    public BlankFinal() {
        j = 1;
        p = new Poppet(1);
    }
    
    public BlankFinal(int x) {
        j = x;
        p = new Poppet(x);
    }
    
    public static void main(String[] args) {
        new BlankFinal();
        new BlankFinal(47);
    }
}

說白了,如果用final修飾資料,咱們就必須在你使用之前進行初始化,可以直接在建構函式中或者在屬性定義時進行初始化。

四、final方法和private

使用 final 方法的原因有兩個。第一個原因是給方法上鎖,防止子類通過覆寫改變方法的行為。這是出於繼承的考慮,確保方法的行為不會因繼承而改變。

class WithFinals {
  // 使用private和final共同修飾,和只使用private修飾效果一樣
  private final void f() {
    System.out.println("WithFinals.f()");
  }
  // 自動添加了 final
  private void g() {
    System.out.println("WithFinals.g()");
  }
}

class OverridingPrivate extends WithFinals {
  private final void f() {
    System.out.println("OverridingPrivate.f()");
  }
  private void g() {
    System.out.println("OverridingPrivate.g()");
  }
}

class OverridingPrivate2 extends OverridingPrivate {
  public final void f() {
    System.out.println("OverridingPrivate2.f()");
  }
  public void g() {
    System.out.println("OverridingPrivate2.g()");
  }
}

public class FinalOverridingIllusion {
  public static void main(String[] args) {
    OverridingPrivate2 op2 = new OverridingPrivate2();
    op2.f();
    op2.g();
    // 可以向上轉型,但是不能呼叫op.f()或者op.g()
    OverridingPrivate op = op2;
    //- op.f();
    //- op.g();
    // Same here:
    WithFinals wf = op2;
    //- wf.f();
    //- wf.g();
  }
}

如果放開 op2.f() 的註釋,編譯器會報紅,如果進行編譯,會提示 java: f() 在 reuse.OverridingPrivate 中是 private 訪問控制,因為如果是使用 private 或者 final 修飾的方法,它只是隱藏在類內部的程式碼。在 WithFinalsOverridingPrivateOverridingPrivate2中, **f() **和 g() 都是屬於各自的方法,他們之間不存在任何聯絡,細心的小夥伴可能會發現在 OverridingPrivate 和 **OverridingPrivate2 **沒有 @override 註解,如果強行加上 @override 註解,編譯器會報錯,由此看來,它們之間並沒有重寫父類的 private 方法,只是恰好有相同的命名而已。這也不難解釋為什麼呼叫 op.f() 編譯器會報錯。

五、final類

當說一個類是 finalfinal 關鍵字在類定義之前),就意味著它不能被繼承。之所以這麼做,是因為類的設計就是永遠不需要改動,或者是出於安全考慮不希望它有子類。

class SmallBrain {}

final class Dinosaur {
    int i = 7;
    int j = 1;
    SmallBrain x = new SmallBrain();
    
    void f() {}
}

//- class Further extends Dinosaur {}
public class Jurassic {
    public static void main(String[] args) {
        Dinosaur n = new Dinosaur();
        n.f();
        n.i = 40;
        n.j++;
    }
}

如果放開註釋,顯而易見的會出現error: Cannot extend final class 'Dinosaur'錯誤,因為 Dinosaur 是不能被繼承的。當然,被 final 修飾的類,其中包含的方法都被隱式的定義為 final