許多編程語言都有某種方法,來向編譯器告知一塊數據是恆定不變的。有時候數據的恆定不變是很有用的,例如:
- 一個編譯時恆定不變的常量
- 一個在運行時初始化,而你不希望它被改變的變量
對於編譯期常量的這種情況,編譯器可以將該常量值代入任何可能用到它的計算式中,也就是說,可以在編譯期就執行計算式,這減輕了一些運行時的負擔。在Java中,這類常量必須是基本類型的,並且以final表示,在對這個常量在定義時,或者構造函數中,必須進行賦值。
一個即是static又是final的域只占一段不能改變的存儲空間。
final數據
當final應用於對象引用時,而不是基本類型時,其含義讓人疑惑。對基本類型使用final不能改變的是他的數值。而對於對象引用,不能改變的是他的引用,而對象本身是可以修改的。一旦一個final引用被初始化指向一個對象,這個引用將不能再指向其他對象。java並未提供對任何對象恆定不變的支持。這一限制也通用適用於數組,它也是對象。
package com.bupt.java.test; import java.util.Random; class Value{ int i; final int j; public Value( int i , int j){ this.i = i; this.j = j; } } /* * 對基本類型使用final不能改變的是它的數值 * 而對於對象引用,不能改變的是它的引用,而對象本身是可以修改的 * 一旦一個final引用被初始化只想一個對象,這個引用不能再指向其他對象 * */ public class FinalData { private static Random rand = new Random(47); private String id; public FinalData( String id){ this.id = id; } //編譯時常量,根據慣例,既是static又是final的域將大寫表示 private final int valueOne = 9; private static final int VALUE_TWO = 99; //典型的公共常量 public static final int VALUE_THREE = 39; //運行時常量 private final int i4 = rand.nextInt(20); static final int INT_5 = rand.nextInt(20); private Value v1 = new Value(11,22); private final Value v2 = new Value(22,33); private static final Value VAL_3 = new Value(33,44); //數組Arrays private final int[] a = {1,2,3,4,5,6}; public String toString(){ return id + ": " + "i4 = " + i4 + ", INT_5 = " + INT_5; } public static void main(String[] args){ FinalData fd1 = new FinalData("fd1"); //fd1.valueOne++; //不能對終態字段 FinalData.valueOne 賦值 fd1.v2.i++; // System.out.println(fd1.v2.i); //fd1.v2.j++; //不能對終態字段 Value.j 賦值 fd1.v1 = new Value(12,23); // ok -- not final for ( int i = 0 ; i < fd1.a.length ; i++ ) { fd1.a[i]++; //Object isn't constant! } //fd1.v2 = new Value(23,34);//不能對終態字段 FinalData.v2 賦值 //fd1.VAL_3 = new Value(34,45); //不能對終態字段 FinalData.VAL_3 賦值 //fd1.a = new int[3];//不能對終態字段 FinalData.a 賦值 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 = 15, INT_5 = 18 Creating new FinalData fd1: i4 = 15, INT_5 = 18 fd2: i4 = 13, INT_5 = 18
由於valueOne和VALUE_TOW都是帶有編譯時數字的final基本類型,所以它們二者均可以用作編譯期常量,並且沒有重大區別。
VALUE_THREE是一種更加典型的對常量進行定義的方式:定義為public,可以被任何人訪問;定義為static,則強調只有一份;定義為final,說明它是個常量。請注意被static和final修飾的變量全用大寫字母命名,並且之間用下划線隔開。
我們不能因為某些數據是final的就認為在編譯時可以知道它的值。在運行時使用隨機數來初始化i4和INT5時說明這一點。實例中fd1和fd2中i4分別被初始化為15和13,INT_5的值是不可以通過創建第二個FinalData實例加以改變的,這是因為INT_5是被static修飾的。
空白final
java也許生成“空白final”,所謂空白final是指被聲明為final但又未給初值的域。無論什么情況下編譯器都會保證final域在使用前初始化。但空白final在final的使用上提供了很大的靈活性,為此,一個final域可以根據某些對象有所不同,卻又保持恆定不變的特性。
package com.bupt.java.test; class Poppet{ private int i; public Poppet(int i){ this.i = i; } public int getI() { return i; } public void setI(int i) { this.i = i; } } public class BlankFinal { private final int i = 0; //Initialized final private final int j; //blank final private final Poppet p; // blank final reference public BlankFinal(){ j = 1; p = new Poppet(1); } public BlankFinal( int x) { j = x; p = new Poppet(x); } public static void main(String[] args){ BlankFinal b1 = new BlankFinal(); BlankFinal b2 = new BlankFinal(47); System.out.println("b1.j="+b1.j+"\t\t b1.p.i="+b1.p.getI()); System.out.println("b2.j="+b2.j+"\t\t b2.p.i="+b2.p.getI()); } }
運行結果:
b1.j=1 b1.p.i=1
b2.j=47 b2.p.i=47
final參數
java中也會將參數列表中的參數以聲明的方式指明為final。這意味着你無法改變參數所指向的對象。例如:
package com.bupt.java.test; class Gizmo{ public void spin(String temp){ System.out.println(temp+" Method call Gizmo.spin()"); } } public class FinalArguments { void with(final Gizmo g){ //g = new Gizmo(); //不能對終態局部變量 g 賦值。它必須為空白,並且不使用復合賦值 } void without(Gizmo g) { g = new Gizmo(); g.spin("without"); } // void f(final int i){ // i++; // }不能對終態局部變量 i 賦值。它必須為空白,並且不使用復合賦值 int g(final int i){ return i + 1; } public static void main(String[] args){ FinalArguments bf = new FinalArguments(); bf.without(null); bf.with(null); System.out.println("bf.g(10)="+bf.g(10)); } }
運行結果:
without Method call Gizmo.spin()
bf.g(10)=11
使用final方法有兩個原因。第一個原因是把方法鎖定,以防止任何繼承它的類修改它的含義。這是出於設計的考慮:想要確保在繼承中使用的方法保持不變,並且不會被覆蓋。過去建議使用final方法的第二個原因是效率。在java的早期實現中,如果將一個方法指明為final,就是同意編譯器將針對該方法的所有調用都轉為內嵌調用。當編譯器發現一個final方法調用命令時,它會根據自己的謹慎判斷,跳過插入程序代碼這種正常的調用方式而執行方法調用機制(將參數壓入棧,跳至方法代碼處執行,然后跳回並清理棧中的參數,處理返回值),並且以方法體中的實際代碼的副本來代替方法調用。這將消除方法調用的開銷。當然,如果一個方法很大,你的程序代碼會膨脹,因而可能看不到內嵌所帶來的性能上的提高,因為所帶來的性能會花費於方法內的時間量而被縮減。在最近的java版本中,虛擬機(特別是hotspot技術)可以探測到這些情況,並優化去掉這些效率反而降低的額外的內嵌調用,因此不再需要使用final方法來進行優化了。事實上,這種做法正逐漸受到勸阻。在使用java se5/6時,應該讓編譯器和JVM去處理效率問題,只有在想明確禁止覆蓋式,才將方法設置為final的。
final和private關鍵字
類中的所有private方法都是隱式的制定為final的。由於你無法訪問private方法,你也就無法覆蓋它。可以對private方法添加final修飾詞,但這是毫無意義的。例如:
package com.bupt.java.test; class WithFinals{ private final void f(){ System.out.println("WithFinals.f()"); } private final void g(){ System.out.println("WithFinals.g()"); } } class OverridingPrivate extends WithFinals { private final void f() { System.out.println("OverridingPrivate.f()"); } private void g() { System.out.println("OverridingPrivate.g()"); } } class OverridingPrivate2 extends OverridingPrivate { public final void f() { System.out.println("OverridingPrivate2.f()"); } public void g() { System.out.println("OverridingPrivate2.g()"); } } public class OverrideFinal { public static void main(String[] args){ WithFinals w1 = new WithFinals(); // w1.f(); //類型 WithFinals 中的方法 f()不可視,無法訪問私有方法 // w1.g();//類型 WithFinals 中的方法 g()不可視,無法訪問私有方法 OverridingPrivate w2 = new OverridingPrivate(); // w2.f(); // w2.g(); OverridingPrivate2 w3 = new OverridingPrivate2(); w3.f(); w3.g(); } }
運行結果:
OverridingPrivate2.f()
OverridingPrivate2.g()
final類
當將類定義為final時,就表明你不打算繼承該類,而且也不允許別人這么做。換句話說,出於某種考慮,你對該類的設計不需要做任何改動,或者出於安全的考慮,你不希望他有子類:
package com.bupt.java.test; class SmallBrain{ } final class Dinosaur{ int i = 7; int j = 1; SmallBrain x = new SmallBrain(); void f(){ System.out.println("Dinosaur.f()"); } } //class a extends Dinosaur{ // //}類型 a 不能成為終態類 Dinosaur 的子類 public class Jurassic { public static void main(String[] args){ Dinosaur n = new Dinosaur(); n.f(); n.i = 40; n.j++; System.out.println("n.i="+n.i); System.out.println("n.j="+n.j); } }
運行結果:
Dinosaur.f() n.i=40 n.j=2
由於final是無法繼承的,所以被final修飾的類中的發你過分都隱式的制定為final,以為你無法覆蓋他們。在final類中可以給方法添加final,但這不會產生任何意義。
總結
- final類不能被繼承,沒有子類,final類中的方法默認是final的,但是final類中的成員變量默認不是final的。
- final方法不能被子類覆蓋,但可以被繼承。
- final成員變量表示常量,只能被賦值一次,賦值后值不再改變。
- final不能用於修飾構造方法。