概述
final 的意思是恒定不變的,和 C 語言中的 const 關鍵字類似,指的是恒定不變的量。
根據其修飾的主體的不同,含義會有一定差異,Java 中一共可以修飾三種情況,即資料、方法和類。
final 資料
使用 final 修飾的資料通常是指一個常量,例如:
上面代碼中的變量
i
在運作時的值是不可變的。
使用 final 關鍵字有兩個目的:
- 作為一個永不改變的編譯時常量
- 在運作時被初始化後,永不改變
第一點中的編譯時常量是指在代碼中已經确定常量的值,而不是運作時進行初始化,是以該值可以在編譯時用到其他計算式中進行計算,進而提高效率。
那麼當我們使用 final 修飾一個引用對象時,final 限定的是對象内的成員變量不可變還是這個引用不可變呢?
答案是引用不可變,例如下面代碼:
class apple {
public String str;
apple(String str){
this.str = str;
System.out.println(str);
}
}
public class text{
public final apple a = new apple("apple1");
public static void main(String [] args) {
a = new apple("apple2") // 報錯,a 是 final 的,無法修改。
a.str = "apple2"; // 通過,對象内部的變量 str 不是 final 的,是以可以改變。
}
}
我們再來看一下,當 final 遇上 static 會發生什麼呢?
使用 final 和 static 同時修飾的,其重點是在強調隻有一份并且不可改變,通常這樣的常量使用大寫字母加下劃線分隔來命名。例如:
class value {
String str;
value(String a) {
this.str = a;
}
public String toString() {
return str;
}
}
public class test {
private static Random r = new Random(47);
public static final int VALUE_A = r.nextInt(20);
public static final value VALUE_B = new value("value1");
public final int value = r.nextInt(20);
public static void main(String [] args) {
test t1 = new test();
test t2 = new test();
t1.VALUE_B = new value("value2");// 報錯,final 修飾的引用不可變
System.out.println("t1:"+t1.VALUE_A+"--"+t1.VALUE_B+"--"+t1.value);
System.out.println("t2:"+t2.VALUE_A+"--"+t2.VALUE_B+"--"+t2.value);
}
}
/*output:
t1:18--value1--15
t2:18--value1--13
*/
通過上述代碼可以發現,
VALUE_A
的值是唯一的,而
value
的值在不同對象中值不一樣。
即帶 static 的在運作時不同對象中始終是同一個值,并且不可變,不帶 static 修飾的不同對象中值不同,運作時同樣不可變。并且 final 修飾引用,意味着無法将該引用指向新的對象。
在 final 修飾資料的部分,還有一種特殊情況:空白 final。
空白 final 的存在提供了更大的靈活性,可以做到根據對象不同而有所不同,但又同時保持了不變的特性。
class person {
String say;
person(String say) {
this.say = say;
}
}
public class test {
private final int a = 9;
private final int b;// 空白 final
private final person p1 = new person("abc");
private final person p2;// 空白 final
test(int x, person p) {
this.b = x;
this.p2 = p;
}
public static void main(String args) {
test t1 = new test(10, new person("t1"));
test t2 = new test(19, new person("t2"));
}
}
最後,final 還可以用于修飾方法中的參數,如此可以確定該參數在方法體内不可變,通常修飾引用類型參數。
class person {
public say() {}
}
public class test {
void personSay(person p) {
p = new person();// 沒問題
p.say();
}
void personSay1(final person p) {
p = new person();// 報錯,final 修飾的引用不可變
}
}
final 方法
使用 final 修飾方法的原因有兩個:
- 出于設計的考慮,将方法鎖定,確定方法不被繼承
- 提高效率,final 修飾的方法在調用處,在編譯階段會将方法體複制到調用處,是以不必額外消耗方法調用的時間,這被稱為内嵌調用。
上述第二點中,如果 final 修飾的代碼塊很大,那麼将會得不償失,是以在Java SE5/6 以上版本中,對于 final 方法塊代碼很多的情況,虛拟機可以探測到這些情況,并且直接優化掉這些降低效率的内嵌調用。
是以在 Java SE5/6 以上版本時,效率問題交給虛拟機和編譯器考慮,我們隻需要關注第一點即可。
值得注意的是,private 修飾的方法本身就不能被子類所繼承,是以隐含是 final 的,當然,我們顯示的寫上 final 也沒問題,不過這樣做非但沒有任何意義,還會引起混淆。
class WithFinals {
private final void f() {
System.out.println("WithFinals_f()");
}
private void g() {
System.out.println("WithFinals_g()");
}
}
class test1 extends WithFinals {
private final void f() {
System.out.println("test1_f()");
}
private void g() {
System.out.println("test1_g()");
}
}
上述代碼中,可能很容易會産生這樣的疑惑:f() 函數
明明是 final 的,怎麼子類中依然可以繼承呢?
要弄明白這個疑惑,首先得知道子類可以重寫父類的哪些方法,實際上,子類隻能夠重寫父類接口的方法,而 private 方法是父類的私有代碼的一部分,并不是父類接口的一部分。
是以,上述代碼中,雖然父類和子類都存在 f() 方法,但是它們并不是重寫的關系,在實際開發中,為了避免這種混淆,通常在子類中重寫父類的方法前加
@Override
标注。
final 類
當某個類被标記為 final 時,表示這個類不接受任何類的繼承。也就是這個類的設計不需要做任何變動,不希望它有子類。
如此一來,final 類中的方法實際上是隐式定義為了 final 方法,你可以顯示标注為 final,但并沒有實際意義。
final 使用注意事項
在使用 final 時,需要格外謹慎和小心,因為當你在設計一個類或方法時,可能你覺得不需要被複用,并且使用 final 修飾,但這可能會妨礙其他程式員在項目中通過繼承或重寫來複用你的類或方法。
值得一提的幾個案例:
- Java 1.0/1.1 中的 Vector 類應用很廣泛,如果其中的方法均沒有 final 修飾,那麼将會更加有用,但遺憾的是設計者不這麼認為。不過令人意外的是,Stack 類卻繼承自 Vector 類,不知道設計者在繼承的時候有沒有意識到 final 方法過于嚴苛。
- Vector 類中許多重要的方法都是同步的,是以會導緻很大的開銷,這直接影響到了 final 帶來的好處。
- 同樣值得注意的是 Hashtable 類,它也是 Java 1.0/1.1 重要的類,卻沒有一個方法是 final 的。當然目前 Hashtable 已經用HashMap代替了。