標籤:
依據上下文環境,java的keywordfinal也存在著細微的差別,但通常指的是“這是無法改變的。”不想改變的理由由兩種:一種是效率,還有一種是設計。因為兩個原因相差非常遠,所以關鍵子final可能被吳用。
接下來介紹一下使用到fianl的三中情況:資料,方法,類。
final資料
很多程式設計語言都有某種方法,來向編譯器告知一塊資料是恒定不變的。有時資料的恒定不變是非常實用的,比如:
1,一個編譯時間恒定不變的常量
2,一個在執行時初始化,而你不希望它被改變。
對於編譯期常量的這樣的情況,編譯器能夠將該常量值代入不論什麼可能用到它的計算式中,也就是說,能夠在編譯期就執行計算式,這減輕了一些執行時的負擔。在java中,這類常量必須是基本類型,而且以final表示。在對這個常量定義時,必須進行賦值。
一個即是static又是fianl的域僅僅佔一段不能改變的儲存空間。
當final應用於對象引用時,而不是基本類型時,其含義有些讓人疑惑。對基本類型使用fianl不能改變的是他的數值。而對於對象引用,不能改變的是他的引用,而對象本身是能夠改動的。一旦一個final引用被初始化指向一個對象,這個引用將不能在指向其它對象。java並未提供對不論什麼對象恒定不變的支援。這一限制也通用適用於數組,它也是對象。
以下的案例示範fianl域的情況。注意,依據慣例,即是static又是fianl的域(即編譯器常量)將用大寫表示,並用下劃切割個單詞:
package reusing;//: reusing/FinalData.java// The effect of final on fields.import java.util.*;import static net.mindview.util.Print.*;class Value { int i; // Package access public Value(int i) { this.i = i; }}public class FinalData { private static Random rand = new Random(47); private String id; public FinalData(String id) { this.id = id; } // Can be compile-time constants: private final int valueOne = 9; private static final int VALUE_TWO = 99; // Typical public constant: public static final int VALUE_THREE = 39; // Cannot be compile-time constants: private final int i4 = rand.nextInt(20); static final int INT_5 = rand.nextInt(20); private Value v1 = new Value(11); private final Value v2 = new Value(22); private static final Value VAL_3 = new Value(33); // 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++; // Error: can‘t change value fd1.v2.i++; // Object isn‘t constant! fd1.v1 = new Value(9); // OK -- not final for(int i = 0; i < fd1.a.length; i++) fd1.a[i]++; // Object isn‘t constant! //! fd1.v2 = new Value(0); // Error: Can‘t //! fd1.VAL_3 = new Value(1); // change reference //! fd1.a = new int[3]; print(fd1); print("Creating new FinalData"); FinalData fd2 = new FinalData("fd2"); print(fd1); print(fd2); }} /* Output:fd1: i4 = 15, INT_5 = 18Creating new FinalDatafd1: i4 = 15, INT_5 = 18fd2: i4 = 13, INT_5 = 18*/
因為valueOne和VALUE_TWO都是帶有編譯時間數值的fianl基本類型,所以它們二者均能夠用作編譯期常量,而且沒有重大差別。VALUE_THREE是一種更加典型的對常量進行定義的方式:定義為public,能夠被不論什麼人訪問;定義為static,則強調僅僅有一份;定義為fianl,這說明它是個常量。請注意帶有恒定初始值(即,編譯期常量)的final static基本類型全用大寫字母命名,而且字母與字母之間用底線隔開。
我們不能由於某些資料是fianl的就覺得在編譯時間能夠知道它的值。在執行時使用隨機數來初始化i4和INT_5的值叫說明了這一點。案例部分也展示了將fianl資料定義為static和非static的差別。此差別僅僅有當數值在執行時內被初始化時才會顯現,這是由於在編譯器對編譯時間的數值一視同仁(而且他們可能由於最佳化而消失)。當執行時會看見這個差別。請注意,在此fd1和fd2中i4的值是唯一的,每次都會被初始化為15,13。INT_5的值是不能夠通過建立第二個FinalData對象加以改變的。這是由於他是static的,在裝載類時(也就是第一次建立這個類對象時)已經被初始化,而不是每次建立都初始化。
假設看上面的案例來理解我標記顏色的的部分有點困難的話,請看以下的案例:
public class B3 {static Random r =new Random(12);final int int1= r.nextInt(100);//產生0-99的隨機數static final int INT_2= r.nextInt(100);public static void main(String[] args) {B3 b1=new B3();System.out.println("int1:"+b1.int1+" INT_2:"+b1.INT_2);B3 b2=new B3();//b2.INT_2=100;//錯誤的賦值System.out.println("int1:"+b2.int1+" INT_2:"+b2.INT_2);}}
啟動main()先啟動並執行是B3 b1=new B3();,建立B3的第一個對象,這將會先初始化static final int INT_2= r.nextInt(100);,然後是初始化final int int1= r.nextInt(100);,所以第一條輸出語句的結果是int1:12 INT_2:66。接下來建立B3的第二個對象,這也會導致B3類中成員的初始化,但static final int INT_2= r.nextInt(100);不會在被初始化,為什麼前面已經提過。輸出的結果是int1:56 INT_2:66。兩次的輸出INT_2的值都是一樣的。
在說回我們的第一個案例,V1到VAL_3說明final引用的意義。正如在main()方法中看見的,能夠改變對象數組a的值,但不能將a的引用指向還有一個對象。看起來使基本類型成為fianl比參考型別成為final的用處大。
java或許產生"空白final",所謂空白final是指被聲明為final但又未給初值的域。不管什麼情況下編譯器都會保證final域在使用前初始化。但空白final在fianl的使用上提供了非常大的靈活性,為此,一個fianl域能夠依據某些對象有所不同,卻又保持恒定不變的特性。以下的案例說明了一點。
class Poppet { private int i; Poppet(int ii) { i = ii; }}public class BlankFinal { private final int i = 0; // Initialized final private final int j; // Blank final private final Poppet p; // Blank final reference // Blank finals MUST be initialized in the constructor: public BlankFinal() { j = 1; // Initialize blank final p = new Poppet(1); // Initialize blank final reference } public BlankFinal(int x) { j = x; // Initialize blank final p = new Poppet(x); // Initialize blank final reference } public static void main(String[] args) { new BlankFinal(); new BlankFinal(47); }} //
final 參數
java中或許將參數列表中的參數以聲明的方式聲指明為final。這意味著你無發改變參數所指向的對象。
class Gizmo { public void spin() {}}public class FinalArguments { void with(final Gizmo g) { //! g = new Gizmo(); // Illegal -- g is final } void without(Gizmo g) { g = new Gizmo(); // OK -- g not final g.spin(); } // void f(final int i) { i++; } // Can‘t change // You can only read from a final primitive: int g(final int i) { return i + 1; } public static void main(String[] args) { FinalArguments bf = new FinalArguments(); bf.without(null); bf.with(null); }} //
方法f()g()展示了基本類型的參數被指定為final是所出現的結果:你能夠讀參數,但不能改動參數。這一特性僅僅要用來向匿名內部類傳遞資料。
final 方法
使用final方法有兩個原因。第一個原因是把方法鎖定,以防止不論什麼繼承它的類改動它的含義。這是出於設計的考慮:想要確保在繼承中使用的方法保持不變,而且不會被覆蓋。
過去建議使用final方法的第二個原因是效率。在java的早期實現中,假設將一個方法指明為fianl,就是允許編譯器將針對該方法的全部調用都轉為內嵌調用。當編譯器發現一個final方法調用命令時,它會依據自己的謹慎推斷,跳過插入程式碼這樣的正常的調用方式而運行方法調用機制(將參數壓入棧,跳至方法代碼處運行,然後跳回並清理棧中的參數,處理傳回值),而且以方法體中的實際代碼的副本來取代方法調用。這將消除方法調用的開銷。當然,假設一個方法非常大,你的程式碼會膨脹,因而可能看不到內嵌所帶來的效能上的提高,由於所帶來的效能會花費於方法內的時間量而被縮減。
上面標顏色的地方不太懂。不知道那位看過Java編程思想和知道的高人給解釋解釋。
在最進的java版本號碼中,虛擬機器(特別是hotspot技術)能夠探測到這些情況,並最佳化去掉這些效率反而減少的額外的內嵌調用,因此不再須要使用final方法來進行最佳化了。其實,這樣的做法正逐漸受到勸阻。在使用java se5/6時,應該讓編譯器和JVM去處理效率問題,僅僅有在想明白禁止覆蓋式,才將方法設定為fianl的。
final和privatekeyword
類中的全部private方法都是隱式的制定為final的。因為你無法訪問private方法你也就無法覆蓋它。能夠對private方法加入final修飾詞,但這毫無意義。
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()"); }}
"覆蓋"僅僅有在某方法是基類介面的一部分時才會發生。即,必須將一個對象向上轉型為它的基類並條用同樣的方法。假設某方法是private的,它就不是基類介面的一部分。它僅是一些隱藏於類中的程式碼,假設一個基類中存在某個private方法,在衍生類別中以同樣的名稱建立一個public,protected或包訪問許可權方法的話,該方法僅僅只是是與基類中的方法有同樣的名稱而已,並沒有覆蓋基類方法。由於private方法無法觸及且有非常好的隱藏性,所以把它看成是由於他所屬類的組織結的原因而存在外,其它不論什麼事物都不用考慮。
final 類
當將類定義為final時,就表明了你不打算繼承該類,並且也不或許別人這樣做。換句話說,出於某種考慮,你對該類的設計永不須要做不論什麼變動,或者出於安全的考慮,你不希望他有子類。
class SmallBrain {}final class Dinosaur { int i = 7; int j = 1; SmallBrain x = new SmallBrain(); void f() {}}//! class Further extends Dinosaur {}// error: Cannot extend final class ‘Dinosaur‘public class Jurassic { public static void main(String[] args) { Dinosaur n = new Dinosaur(); n.f(); n.i = 40; n.j++; }}
請注意,final類的域能夠依據個人的意願選擇是或不是final。不論類是否被定義為final,相同的規則相同適用於定義為final的域。然而,由於final是無法繼承的,所以被final修飾的類中的方法都隱式的制定為fianl,由於你無法覆蓋他們。在fianl類中能夠給方法加入final,但這不會產生不論什麼意義。
java final keyword