java final 關鍵字

 轉自:http://android.blog.51cto.com/268543/384614 

  根據上下文環境,java的關鍵字final也存在着細微的區別,但通常指的是“這是無法改變的。”不想改變的理由由兩種:一種是效率,另一種是設計。由於兩個原因相差很遠,所以關鍵子final可能被吳用。

   接下來介紹一下使用到fianl的三中情況:數據,方法,類。

  

   final數據

   許多編程語言都有某種方法,來向編譯器告知一塊數據是恆定不變的。有時數據的恆定不變是很有用的,例如:

1,一個編譯時恆定不變的常量

2,一個在運行時初始化,而你不希望它被改變。

   對於編譯期常量的這種情況,編譯器可以將該常量值代入任何可能用到它的計算式中,也就是說,可以在編譯期就執行計算式,這減輕了一些運行時的負擔。在java中,這類常量必須是基本類型,並且以final表示。在對這個常量定義時,必須進行賦值。

   一個即是static又是fianl的域只佔一段不能改變的存儲空間。

   當final應用於對象引用時,而不是基本類型時,其含義有些讓人疑惑。對基本類型使用fianl不能改變的是他的數值。而對於對象引用,不能改變的是他的引用,而對象本身是可以修改的。一旦一個final引用被初始化指向一個對象,這個引用將不能在指向其他對象。java並未提供對任何對象恆定不變的支持。這一限制也通用適用於數組,它也是對象。

   下面的事例示範fianl域的情況。注意,根據慣例,即是static又是fianl的域(即編譯器常量)將用大寫表示,並用下劃分割個單詞:

[java] view plaincopy
  1.  package reusing;  
  2.   
  3. //: reusing/FinalData.java  
  4. // The effect of final on fields.  
  5. import java.util.*;  
  6. import static net.mindview.util.Print.*;  
  7.   
  8. class Value {  
  9.   int i; // Package access  
  10.   public Value(int i) { this.i = i; }  
  11. }  
  12.   
  13.   
  14.   
  15. public class FinalData {  
  16.   private static Random rand = new Random(47);  
  17.   private String id;  
  18.   public FinalData(String id) { this.id = id; }  
  19.   // Can be compile-time constants:  
  20.   private final int valueOne = 9;  
  21.   private static final int VALUE_TWO = 99;  
  22.   // Typical public constant:  
  23.   public static final int VALUE_THREE = 39;  
  24.   // Cannot be compile-time constants:  
  25.   private final int i4 = rand.nextInt(20);  
  26.   static final int INT_5 = rand.nextInt(20);  
  27.   private Value v1 = new Value(11);  
  28.   private final Value v2 = new Value(22);  
  29.   private static final Value VAL_3 = new Value(33);  
  30.   // Arrays:  
  31.   private final int[] a = { 123456 };  
  32.   public String toString() {  
  33.     return id + ": " + "i4 = " + i4 + ", INT_5 = " + INT_5;  
  34.   }  
  35.   
  36.   
  37.   public static void main(String[] args) {  
  38.     FinalData fd1 = new FinalData("fd1");  
  39.     //! fd1.valueOne++; // Error: can't change value  
  40.     fd1.v2.i++; // Object isn't constant!  
  41.     fd1.v1 = new Value(9); // OK -- not final  
  42.     for(int i = 0; i < fd1.a.length; i++)  
  43.       fd1.a[i]++; // Object isn't constant!  
  44.     //! fd1.v2 = new Value(0); // Error: Can't  
  45.     //! fd1.VAL_3 = new Value(1); // change reference  
  46.     //! fd1.a = new int[3];  
  47.     print(fd1);  
  48.     print("Creating new FinalData");  
  49.     FinalData fd2 = new FinalData("fd2");  
  50.     print(fd1);  
  51.     print(fd2);  
  52.   }  
  53. }   
  54.   
  55. /* Output: 
  56. fd1: i4 = 15, INT_5 = 18 
  57. Creating new FinalData 
  58. fd1: i4 = 15, INT_5 = 18 
  59. fd2: i4 = 13, INT_5 = 18 
  60. */  

     由於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的,在裝載類時(也就是第一次創建這個類對象時)已經被初始化,而不是每次創建都初始化。

   


如果看上面的事例來理解我標記顏色的的部分有點困難的話,請看下面的事例:

   

 

[java] view plaincopy
  1. public class B3 {  
  2.     static Random r =new Random(12);  
  3.     final int int1= r.nextInt(100);//產生0-99的隨機數  
  4.     static final int INT_2= r.nextInt(100);  
  5.       
  6.   
  7.     public static void main(String[] args) {  
  8.         B3 b1=new B3();  
  9.         System.out.println("int1:"+b1.int1+"    INT_2:"+b1.INT_2);  
  10.         B3 b2=new B3();  
  11.         //b2.INT_2=100;//錯誤的賦值  
  12.         System.out.println("int1:"+b2.int1+"    INT_2:"+b2.INT_2);  
  13.   
  14.     }  
  15.   
  16. }  

啓動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域可以根據某些對象有所不同,卻又保持恆定不變的特性。下面的事例說明了一點。

 

[java] view plaincopy
  1. class Poppet {  
  2.   private int i;  
  3.   Poppet(int ii) { i = ii; }  
  4. }  
  5.   
  6. public class BlankFinal {  
  7.   private final int i = 0// Initialized final  
  8.   private final int j; // Blank final  
  9.   private final Poppet p; // Blank final reference  
  10.   // Blank finals MUST be initialized in the constructor:  
  11.   public BlankFinal() {  
  12.     j = 1// Initialize blank final  
  13.     p = new Poppet(1); // Initialize blank final reference  
  14.   }  
  15.   public BlankFinal(int x) {  
  16.     j = x; // Initialize blank final  
  17.     p = new Poppet(x); // Initialize blank final reference  
  18.   }  
  19.   public static void main(String[] args) {  
  20.     new BlankFinal();  
  21.     new BlankFinal(47);  
  22.   }  
  23. //  

 

final 參數

      java中也許將參數列表中的參數以聲明的方式聲指明爲final。這意味着你無發改變參數所指向的對象。

[java] view plaincopy
  1. class Gizmo {  
  2.   public void spin() {}  
  3. }  
  4.   
  5. public class FinalArguments {  
  6.   void with(final Gizmo g) {  
  7.     //! g = new Gizmo(); // Illegal -- g is final  
  8.   }  
  9.   void without(Gizmo g) {  
  10.     g = new Gizmo(); // OK -- g not final  
  11.     g.spin();  
  12.   }  
  13.   // void f(final int i) { i++; } // Can't change  
  14.   // You can only read from a final primitive:  
  15.   int g(final int i) { return i + 1; }  
  16.   public static void main(String[] args) {  
  17.     FinalArguments bf = new FinalArguments();  
  18.     bf.without(null);  
  19.     bf.with(null);  
  20.   }  
  21. //  

方法f()g()展示了基本類型的參數被指定爲final是所出現的結果:你可以讀參數,但不能修改參數。這一特性只要用來向匿名內部類傳遞數據。

final 方法

   使用final方法有兩個原因。第一個原因是把方法鎖定,以防止任何繼承它的類修改它的含義。這是出於設計的考慮:想要確保在繼承中使用的方法保持不變,並且不會被覆蓋。

   過去建議使用final方法的第二個原因是效率。在java的早期實現中,如果將一個方法指明爲fianl,就是同意編譯器將針對該方法的所有調用都轉爲內嵌調用。當編譯器發現一個final方法調用命令時,它會根據自己的謹慎判斷,跳過插入程序代碼這種正常的調用方式而執行方法調用機制(將參數壓入棧,跳至方法代碼處執行,然後跳回並清理棧中的參數,處理返回值),並且以方法體中的實際代碼的副本來代替方法調用。這將消除方法調用的開銷。當然,如果一個方法很大,你的程序代碼會膨脹,因而可能看不到內嵌所帶來的性能上的提高,因爲所帶來的性能會花費於方法內的時間量而被縮減。

    上面標顏色的地方不太懂。不知道那位看過Java編程思想和知道的高人給解釋解釋。

    在最進的java版本中,虛擬機(特別是hotspot技術)可以探測到這些情況,並優化去掉這些效率反而降低的額外的內嵌調用,因此不再需要使用final方法來進行優化了。事實上,這種做法正逐漸受到勸阻。在使用java se5/6時,應該讓編譯器和JVM去處理效率問題,只有在想明確禁止覆蓋式,纔將方法設置爲fianl的。

    final和private關鍵字

   類中的所有private方法都是隱式的制定爲final的。由於你無法訪問private方法你也就無法覆蓋它。可以對private方法添加final修飾詞,但這毫無意義。

[java] view plaincopy
  1. class WithFinals {  
  2.   // Identical to "private" alone:  
  3.   private final void f() { print("WithFinals.f()"); }  
  4.   // Also automatically "final":  
  5.   private void g() { print("WithFinals.g()"); }  
  6. }  
  7.   
  8. class OverridingPrivate extends WithFinals {  
  9.   private final void f() {  
  10.     print("OverridingPrivate.f()");  
  11.   }  
  12.   private void g() {  
  13.     print("OverridingPrivate.g()");  
  14.   }  
  15. }  
  16.   
  17. class OverridingPrivate2 extends OverridingPrivate {  
  18.   public final void f() {  
  19.     print("OverridingPrivate2.f()");  
  20.   }  
  21.   public void g() {  
  22.     print("OverridingPrivate2.g()");  
  23.   }  
  24. }  

     "覆蓋"只有在某方法是基類接口的一部分時纔會發生。即,必須將一個對象向上轉型爲它的基類並條用相同的方法。如果某方法是private的,它就不是基類接口的一部分。它僅是一些隱藏於類中的程序代碼,如果一個基類中存在某個private方法,在派生類中以相同的名稱創建一個public,protected或包訪問權限方法的話,該方法只不過是與基類中的方法有相同的名稱而已,並沒有覆蓋基類方法。由於private方法無法觸及且有很好的隱藏性,所以把它看成是因爲他所屬類的組織結的原因而存在外,其他任何事物都不用考慮。

    final 類

    當將類定義爲final時,就表明了你不打算繼承該類,而且也不也許別人這樣做。換句話說,出於某種考慮,你對該類的設計永不需要做任何變動,或者出於安全的考慮,你不希望他有子類。

[java] view plaincopy
  1. class SmallBrain {}  
  2.   
  3. final class Dinosaur {  
  4.   int i = 7;  
  5.   int j = 1;  
  6.   SmallBrain x = new SmallBrain();  
  7.   void f() {}  
  8. }  
  9.   
  10. //! class Further extends Dinosaur {}  
  11. // error: Cannot extend final class 'Dinosaur'  
  12.   
  13. public class Jurassic {  
  14.   public static void main(String[] args) {  
  15.     Dinosaur n = new Dinosaur();  
  16.     n.f();  
  17.     n.i = 40;  
  18.     n.j++;  
  19.   }  
  20. }   

    請注意,final類的域可以根據個人的意願選擇是或不是final。不論類是否被定義爲final,相同的規則同樣適用於定義爲final的域。然而,由於final是無法繼承的,所以被final修飾的類中的方法都隱式的制定爲fianl,因爲你無法覆蓋他們。在fianl類中可以給方法添加final,但這不會產生任何意義。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章