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。