轉自: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的域(即編譯器常量)將用大寫表示,並用下劃分割個單詞:
- 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 = 18
- Creating new FinalData
- fd1: i4 = 15, INT_5 = 18
- fd2: 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和private關鍵字
類中的所有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,但這不會產生任何意義。