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。