在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()); // bi=25; // b.list=new ArrayList<Bat>(); System.out.println("I=" + bi + " List Type:" + b.list.getClass()); b = new Bat(23, new ArrayList<Bat>()); b.list.add(new Bat()); System.out.println("I=" + bi + " List Type:" + b.list.getClass()); }}
結果:
I=100 List Type:class java.util.LinkedListI=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=3Creating new FinalDatafd1:i4:6, i5=3fd2: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// callerpublicclass DangerousStates { private final String[] states = new String[] { "Alabama", "Alaska", "ect" }; public String[] getStates() { return states; }}
同樣,儘管對象引用可以被聲明成final 字段,而它所引用的對象仍可能是可變的。如果想要使用final 字段創建不變的對象,必須防止對數組或可變對象的引用“逃離”你的類。要不用重複克隆該數組做到這一點,一個簡單的方法是將數組轉變成List。
// Immutable -- returns an unmodifiable List insteadpublicclass 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 可以防止今後這類不兼容的更改。
PS:final,finally和finallize的區別