在Java中聲明屬性、方法和類時,可使用關鍵字final來修飾。final變量即為常量,只能 賦值一次;final方法不能被子類重寫;final類不能被繼承。
1.final成員
聲明 final 字段有助於優化器作出更好的優化決定,因為如果編譯器知道字段的值不會 更改,那麼它能安全地在寄存器中高速緩存該值。final 字段還通過讓編譯器強制該字段為 只讀來提供額外的安全級別。
1.1關於final成員賦值
1)在java中,普通變量可默認初始化。但是final類型的變量必須顯式地初始化。
2)final 成員能且只能被初始化一次。
3)final成員必須在聲明時(在final變量定義時直接給其賦值)或者在構造函數中被初 始化,而不能在其它的地方被初始化。
示例1 Bat.java
public class Bat {
final double PI = 3.14; // 在定義時賦值
final int i; // 因為要在構造函數中進行初始化,所以此處便不可再賦值
final List<Bat> list; // 因為要在構造函數中進行初始化,所以此處便不 可再賦值
Bat() {
i = 100;
list = new LinkedList<Bat>();
}
Bat(int ii, List<Bat> l) {
i = ii;
list = l;
}
public static void main(String[] args) {
Bat b = new Bat();
b.list.add(new Bat());
// b.i=25;
// b.list=new ArrayList<Bat>();
System.out.println("I=" + b.i + " List Type:" + b.list.getClass());
b = new Bat(23, new ArrayList<Bat>());
b.list.add(new Bat());
System.out.println("I=" + b.i + " List Type:" + b.list.getClass());
}
}
結果:
I=100 List Type:class java.util.LinkedList
I=23 List Type:class java.util.ArrayList
在main方法中有兩行語句注釋掉了,如果你去掉注釋,程序便無法通過編譯,這便是說, 不論是i的值或是list的類型,一旦初始化,確實無法再更改。然而b可以通過重新初始化來 指定i的值或list的類型。
1.2 final引用字段的無效初始化
要正確使用final字段有點麻煩,對於其構造子能拋出異常的對象引用來說尤其如此。因 為 final 字段在每個構造器中必須只初始化一次,如果 final 對象引用的構造器可能拋出 異常,編譯器可能會報錯,說該字段沒有被初始化。編譯器一般比較智能化,足以發現在兩 個互斥代碼分支(比如,if...else 塊)的每個分支中的初始化恰好只進行了一次,但是它 對 try...catch 塊通常不會如此“寬容”。
下面這段代碼通常會出現問題。
class Thingie {
public static Thingie getDefaultThingie() {
return new Thingie();
}
}
public class Foo {
private final Thingie thingie;
public Foo() {
try {
thingie = new Thingie();
} catch (Exception e) {
thingie = Thingie.getDefaultThingie();//Error:The final field thingie may already have been assigned
}
}
}
你可以這樣修改。
public class Foo {
private final Thingie thingie;
public Foo() {
Thingie tempThingie;
try {
tempThingie = new Thingie();
} catch (Exception e) {
tempThingie = Thingie.getDefaultThingie();
}
thingie = tempThingie;
}
}
1.3關於final成員使用
當你在類中定義變量時,在其前面加上final關鍵字,那便是說,這個變量一旦被初始化 便不可改變,這裡不可改變的意思對基本類型來說是其值不可變,而對於對象變量來說其引 用不可再變。然而,對象其本身卻是可以被修改的,Java並未提供使任何對象恆定不變的途 徑。這一限制同樣適合數組,它也是對象。
示例2
private final int VAL_ONE=9;
private static final int VAL_TWO=99;
public static final int VAL_THREE=999;
由於VAL_ONE 和VAL_TOW 是帶有編譯期數值的final 原始類型,所以它們二者均可以用作 編譯期常量,並且沒有重大區別。VAL_THREE是一種更加典型的對常量進行定義的方式:定義 為 public,則可以被用於包之外;定義為 static 來強調只有一份;定義為 final 來說明 它是一個常量。
final標記的變量即成為常量,但這個“常量”也只能在這個類的內部使用,不能在類的 外部直接使用。但是當我們用public static final 共同標記常量時,這個常量就成為全局 的常量(一個既是static又是final的字段只占據一段不能改變的存儲空間)。而且這樣定義 的常量只能在定義時賦值,其他地方都不行。
示例3
class Value {
int i;
public Value(int i) {
this.i = i;
}
}
public class FinalData {
private static Random rand = new Random();
private String id;
public FinalData(String id) {
this.id = id;
}
private final int i4 = rand.nextInt(20);
static final int i5 = rand.nextInt(20);
public String toString() {
return id + ":" + "i4:" + i4 + ", i5=" + i5;
}
public static void main(String[] args) {
FinalData fd1 = new FinalData("fd1");
System.out.println(fd1);
System.out.println("Creating new FinalData");
FinalData fd2 = new FinalData("fd2");
System.out.println(fd1);
System.out.println(fd2);
}
}
結果
fd1:i4:6, i5=3
Creating new FinalData
fd1:i4:6, i5=3
fd2:i4:17, i5=3
示例部分展示了將final 數值定義為static(i5) 和非static(i4) 的區別。此區別只 有在數值在運行期內被初始化時才會顯現,這是因為編譯器對編譯期數值一視同仁。(並且 它們可能因優化而消失。)當你運行程序時,就會看到這個區別。請注意,在fd1 和fd2 中 , i5 的值是不可以通過創建第二個FinalData 對象而加以改變的。這是因為它是 static, 在裝載時已被初始化,而不是每次創建新對象時都初始化。
示例4
class Value {
int i;
public Value(int i) {
this.i = i;
}
}
public class … {
private Value v1=new Value(11);
private final Value v2=new Value(22);
private static final Value v3=new Value(33);
…
}
public static void main(String[] args) {
…
fd1.v2.i++;// OK--Object isn't constant!
fd1.v1=new Value(9);//OK--not final
fd1.v2=new Value(0);//Error:Can't change reference
fd1.v3=new Value(1);//Error:Can't change reference
…
}
從v1 到v3 的變量說明了final 引用的意義。正如你在main( )中所看到的,不能因為v2 是final 的,就認為你無法改變它的值。由於它是一個引用,final 意味著你無法將v2 再次 指向另一個新的對象。
示例5
public class … {
private final int[] a={1,2,3,4,5,6};
…
}
public static void main(String[] args) {
…
for(int i=0;i<fd1.a.length;i++)
fd1.a[i]++;// OK--Object isn't constant!
fd1.a=new int[3];//Error:Can't change reference …
}
對數組具有同樣的意義(可以改變它的值,但不能指向一個新的對象),數組是另一種引 用。
1.4解決final數組的局限性
盡管數組引用能被聲明成 final,但是該數組的元素卻不能。這意味著暴露 public final 數組字段的或者通過它們的方法將引用返回給這些字段的類都不是不可改變的。
// Not immutable -- the states array could be modified by a malicious
// callerpublic
class DangerousStates {
private final String[] states = new String[] { "Alabama", "Alaska", "ect" };
public String[] getStates() {
return states;
}
}
同樣,盡管對象引用可以被聲明成 final 字段,而它所引用的對象仍可能是可變的。如 果想要使用 final 字段創建不變的對象,必須防止對數組或可變對象的引用“逃離”你的類 。要不用重復克隆該數組做到這一點,一個簡單的方法是將數組轉變成 List。
// Immutable -- returns an unmodifiable List insteadpublic
class SafeStates {
private final String[] states = new String[] { "Alabama", "Alaska", "ect" };
private final List statesAsList = new AbstractList() {
public Object get(int n) {
return states[n];
}
public int size() {
return states.length;
}
};
public List getStates() {
return statesAsList;
}
}
1.5 關於final參數使用
還有一種用法是定義方法中的參數為final,對於基本類型的變量,這樣做並沒有什麼實 際意義,因為基本類型的變量在調用方法時是傳值的,也就是說你可以在方法中更改這個參 數變量而不會影響到調用語句,然而對於對象變量,卻顯得很實用,因為對象變量在傳遞時 是傳遞其引用,這樣你在方法中對對象變量的修改也會影響到調用語句中的對象變量,當你 在方法中不需要改變作為參數的對象變量時,明確使用final進行聲明,會防止你無意的修改 而影響到調用方法。
1.6 關於內部類中的參數變量
另外方法中的內部類在用到方法中的參變量時,此參數變量必須聲明為final才可使用。
示例6 INClass.java
public class INClass {
void innerClass(final String str) {
class IClass {
IClass() {
System.out.println(str);
}
}
IClass ic = new IClass();
}
public static void main(String[] args) {
INClass inc = new INClass();
inc.innerClass("Hello");
}
}
2.final方法
2.1final方法用途
1)為了確保某個函數的行為在繼承過程中保持不變,並且不能被覆蓋(overridding), 可以使用final方法。
2)class中所有的private和static方法自然就是final。
2.2 final與private關鍵字
類中所有的private方法都隱式地指定是final的。由於無法取用private方法,所以也就 無法覆蓋它。
“覆蓋”只有在某方法是基類的接口的一部分時才會出現。即,必須能將一個對象向上轉 型為它的基本類型並調用相同的方法。如果某方法為private,它就不是基類的接口的一部分 。它僅是一些隱藏於類中的代碼,只不過是具有相同的名稱而已。但如果在導出類以相同的 方法生成一個public、protected或包訪問權限方法的話,該方法就不會產生在基類中出現的 “僅具有相同名稱”的情況。此時,你並沒有覆蓋該方法,僅是生成了一個新的方法。由於 private方法無法觸及而且能有效隱藏,所以除了把它看成是因為它所歸屬的類的組織結構的 原因而存在外,其他任何事物都不需要考慮它。
3.final類
將某個類的整體定義為final 時,該類無法被繼承。而且由於final類禁止繼承,所以 final類中所有的方法都隱式指定為final的,因為無法覆蓋它們。
final 用於類或方法是為了防止方法間的鏈接被破壞。例如,假定類 X 的某個方法的實 現假設了方法 M 將以某種方式工作。將 X 或 M 聲明成 final 將防止派生類以這種方式重 新定義 M,從而導致 X 的工作不正常。盡管不用這些內部相關性來實現 X 可能會更好,但 這不總是可行的,而且使用 final 可以防止今後這類不兼容的更改。
本文出自 “子 孑” 博客,請務必保留此出處 http://zhangjunhd.blog.51cto.com/113473/20664