?? 依據上下文環境,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 = 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和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,但這不會產生不論什么意義。