1. 程式人生 > 實用技巧 >Java中的final關鍵字

Java中的final關鍵字

final關鍵字表示的不可變的。下面討論final關鍵字使用的三種場合:資料、方法以及類。

final資料

1、final屬性

程式中經常需要用到一些“常數”。常數主要應用於兩個方面:

  • 編譯期常數,永遠不會改變
  • 在執行期初始化一個值,不希望它發生改變。

對於編譯期的常數,計算可以在編譯期間提前執行,可以將常數值直接用於程式中。Java中,這種常數必須是基本資料型別。前置關鍵字final宣告。定義時必須提供一個值

class Person {
    final String name;  // name未初始化,編譯出錯
}

如果對物件控制代碼使用final,final會將控制代碼變成一個常數。進行宣告時,必須將控制代碼初始化到一個具體的物件

,而且不能將控制代碼指向另一個物件。

class Person {
    String name = "張三";
}

public class FinalDemo {

    public static void main(String[] args) {
        final Person p = new Person();
        p = new Person();   // Error:無法為最終變數p分配值
    }
}

然而,物件本身是可以修改的。

class Person {
    String name = "張三";
}

public class FinalDemo {
    public static void main(String[] args) {
        final Person p = new Person();
        p.name = "蕭蕭弈寒";
    }
}

一個可能的結果:

name = fd1,i1 = 0, i2 = 6

name = fd2,i1 = 8, i2 = 6

i1,i2是在執行期間隨機產生的資料。

2、空白final

Java1.1允許建立“空白final”,它們屬於特殊欄位。儘管被宣告為final,但是卻未得到一個初始值。即便如此,空白final還是必須在使用之前得到初始化。 示例:

class Person {}

public class FinalDemo {
    final int i;
    final Person p;

    FinalDemo() {
        i = 1;
        p = new Person();
    }

    FinalDemo(int x) {
        i = x;
        p = new Person();
    }

    public static void main(String[] args) {
        FinalDemo fd = new FinalDemo();
    }
}

現在強行要求對final進行賦值處理,要麼在定義欄位時使用一個表示式,要麼在每個構建器中。

3、用final修飾引數

查了一些資料,很多人都說用final修飾方法引數是防止引數在呼叫時被修改。個人認為這種說法其實有兩種理解:一種是變數的實際值不會被修改,另一種是在方法內部不能被修改。無論是基本引數型別還是引用型別,前一種說法都是錯誤的。因為Java是值傳遞

public class FinalDemo {

    static void f(final int i) {
        i++;    // 無法為final變數賦值,編譯錯誤
    }

    public static void main(String[] args) {
        int x = 10;
        f(x);   // ①
    }
}

①處呼叫的f方法只是將x的值賦給了i,實際上i和x是兩個變數。

再看下面的例子:

class Person {
    String name = "張三";
}

public class FinalDemo {

    public static void main(String[] args) {
        final Person p = new Person();
        changeName(p);
        System.out.println(p.name);
    }

    static void changeName(final Person p) {
        p.name = "蕭蕭弈寒";
    }
}

【執行結果】:

蕭蕭弈寒

由此說明,final並不能阻止changeName()方法改變p的內容。接下來,我們刪除修飾引數的final,然後在changeName方法體內改變p指向的例項:

class Person {
    String name = "張三";
}

public class FinalDemo {

    public static void main(String[] args) {
        final Person p = new Person();
        p.name = "蕭蕭弈寒";
        changeName(p);
        System.out.println(p.name);
    }

    static void changeName(Person p) {
        p = new Person();
    }
}

【執行結果】:
changeName中的name:張三
蕭蕭弈寒

我們可以看出,雖然方法體內的p指向了其他物件,但是對於main方法中的p並沒有影響。原因還是Java是值傳遞的。具體的請參考Java值傳遞還是引用傳遞?

final方法

final方法主要有兩個方面的作用:一種是防止任何繼承類覆蓋方法。若希望一個方法的行為在繼承期間保持不變,不可被覆蓋和改寫,就可以採取這種做法。另一種是提高程式執行的效率。將一個方法設成final後,編譯器就會忽略為執行方法呼叫機制而採取的常規程式碼插入方法(將自變數壓入堆疊;跳至方法程式碼並執行它;跳回來;清除堆疊自變數;最後對返回值進行處理)。它會用方法主體內實際程式碼的一個副本來替換方法呼叫。這樣可以避免方法呼叫時的系統開銷。若方法體太大,可能效率也得不到提升。

class Human {
    public final void show() {
        //...
    }
}
public class Man extends Human{
    public void show() {}   //Cannot override the final method from Human
}

類內所有的private方法都自動成為final。由於不能訪問一個private方法,所以它絕對不會被覆蓋。

final類

如果整個類都是final,就表明這個類不允許被繼承。或者出於安全方面的理由,不希望進行子類化。除此之外,或許還考慮執行效率的問題,確保涉及這個類各物件的所有行動都要儘可能地有效。

final class Human {
}

public class Man extends Human{ // The type Man cannot subclass the final class Human
}

注意:資料成員既可以是final,也可以不是。無論類是否被定義成final,應用於final的規則同樣適用於資料成員。

將類定義成final後,結果只是禁止被繼承。由於禁止了繼承,所以一個final類中的所有方法都預設為final。