final關鍵字
java中的final關鍵字通常是指它所修飾的元素“是無法改變的”。而根據它所修飾的元素的不同,所起的作用存在着細微的區別。下面就討論可能用到final的三種情況:數據、方法和類。
final數據
final修飾變量本身並不複雜,就是變量一經初始化就不能再改變(如果是基本數據類型,就是其數值不可以改變;如果是引用類型,就是其不可以再重新指向其他對象)。
關於final成員變量的初始化——總之一句話:就是在構造方法調用結束之前完成final成員變量的初始化。
很多文章都這麼說——其初始化可以在兩個地方,一是其定義處,二是在構造方法中,兩者只能選其一。這種說法是不正確的,final變量可以在任何可以被初始化的地方初始化,但只能被初始化一次。一旦被初始化後就不能再次賦值。作爲成員變量一定要顯式初始化,而作爲臨時變量則可以只定義不初始化(當然也不能引用)——既空白final。下面是final成員變量初始化的小例子:
package com.ygc;
import java.util.Random;
/**
* final成員變量初始化的幾種情況, 可以在定義處初始化,可以在語句塊中初始化,可以在構造方法中初始化。
* 可以採用隨機值進行初始化,甚至可以使用方法進行初始化,只要初始化在構造方法結束前完成,並且只初始化一次就可以。
* */
public class FinalFieldInitialization {
Random rand = new Random(47);
/**
* 可以在定義時初始化, 可以使用隨機值初始化
* */
final int finalIntegerValue = 1;
final int randomFinalIntegerValue = rand.nextInt();
final String finalReference = "final reference";
/**
* 可以在語句塊中初始化
* */
final int finalIntegerValue1;
final int randomFinalIntegerValue1;
final String finalReference1;
{
finalIntegerValue1 = 1;
randomFinalIntegerValue1 = rand.nextInt();
finalReference1 = "final reference";
}
/**
* 可以在構造方法中初始化, 有多少個構造方法就得初始化多少次
* */
final int finalIntegerValue2;
final String finalReference2;
public FinalFieldInitialization() {
finalIntegerValue2 = 1;
finalReference2 = "final reference";
}
public FinalFieldInitialization(int i) {
finalIntegerValue2 = 1;
finalReference2 = "final reference";
}
/**
* 使用方法初始化
* */
public FinalFieldInitialization(String s) {
finalIntegerValue2 = 1;
finalReference2 = getString();
}
private String getString() {
return "final refrence";
}
}
final修飾方法參數
final修飾方法參數的效果也是一樣的,如果修飾的是基本數據類型,則不可以修改它的值;而如果修飾的是引用類型,則不能重新指向其它對象,但對象本身是可以改變的。而final修飾方法參數最常用的目的就是供匿名內部類使用,而且如果想要在匿名內部類中使用局部變量或者方法參數,則必須使用final修飾,而使用外部類的成員變量則不需要使用final修飾。因爲當你在匿名內部類中使用局部變量或方法參數時,編譯器會把它傳遞給匿名內部類的構造方法,然後隱式地對它做一個拷貝,使它成爲匿名內部類的一個成員變量,而如果這個局部變量或方法參數不是final的,那麼他們在匿名內部類的內部或外部就可以被修改(如果是基本數據類型 就可以修改它們的數值;而如果是引用類型,可以修改它們所指向的對象),一旦對這個變量進行了修改,就會導致外部變量和內部變量的不一致,從而產生一些不可預測的錯誤。爲了避免這種情況的發生,匿名內部類在引用局部變量或方法參數時必須使用final修飾。而使用成員變量時,編譯器會隱式地給匿名內部類傳入一個外部類的this引用 ,而這個this引用是final的,所以匿名 內部類可以隨意地訪問外部類的成員。下面是一個小例子:
private String enclosingClassField;
private void updateStatus() {
final StringBuffer stringBuffer = new StringBuffer();
Runnable doUpdateStatus = new Runnable() {
public void run() {
// 你可以使用FinalFieldInitialization.this, 因爲它永遠都是final的
FinalFieldInitialization.this.enclosingClassField = "";
// 下面這種寫法是簡便的寫法,因爲每一個內部類在實例化的時候,編譯器都會隱式地幫它們添加一個外部類的this引用
enclosingClassField = "";
// 你不可以改變stringBuffer的值,但是你可以修改它指向的對象的內容
stringBuffer.append("hello final");
}
};
}
上述說法可以通過查看字節碼得到 驗證,有興趣的同學可以研究一下。
final方法
final修飾方法的第一個原因是把方法鎖定,以防止任何繼承類修改它的定義。既,用final修飾的方法不可以重寫。
final修飾方法的第二個原因是爲了提高效率,在早期的java實現中會起到一定的作用,現在不建議使用,因爲使用final修飾方法並不會得到性能的顯著提升。所以只有當你確定你的方法不想被重寫時,才應該考慮用final修飾。
類中所有private修飾的方法都隱式地被指定爲final的。因爲在繼承類中無法訪問private方法,所以也無法覆蓋。如果你試圖重寫一個父類中的private修飾的方法,實際上你只是定義了一個新的方法。
class WithFinals {
// Identical to "private" alone:
private final void f() {
print("WithFinals.f()");
}
// Also automatically "final":
private void g() {
print("WithFinals.g()");
}
}
class OverridingPrivate extends WithFinals {
private final void f() {
print("OverridingPrivate.f()");
}
private void g() {
print("OverridingPrivate.g()");
}
}
class OverridingPrivate2 extends OverridingPrivate {
public final void f() {
print("OverridingPrivate2.f()");
}
public void g() {
print("OverridingPrivate2.g()");
}
}
public class FinalOverridingIllusion {
public static void main(String[] args) {
OverridingPrivate2 op2 = new OverridingPrivate2();
op2.f();
op2.g();
// You can upcast:
OverridingPrivate op = op2;
// But you can't call the methods:
// ! op.f();
// ! op.g();
// Same here:
WithFinals wf = op2;
// ! wf.f();
// ! wf.g();
}
}
“覆蓋”只有在某方法是基類的接口的一部分時纔會出現。即,必須能將一個對象向上轉型爲它的基本類型並調用相同的方法。如果某方法爲private,它就不是基類接口的一部分。它僅是一些隱藏於類中的程序代碼,只不過具有相同的名稱而已。但如果在導出類中以相同的名稱生成一個public、protected 或包訪問權限(package access)方法的話,此時你並沒有覆蓋該方法,你僅是生成了一個新的方法。
final 類
final修飾類的目的在於你不想有任何類從此類繼承。final類中的所有方法都隱式地是final的,因爲類無法被繼承,所以其中的方法也不會被重寫。但是final類中的域並不是final的,除非 顯示地設置。