前面介紹嵌套類的時候講到了關鍵字static,用static修飾類,該類就變成了嵌套類。從嵌套類的用法可知,其它地方訪問嵌套類之時,無需動態建立外層類的實例,直接建立嵌套類的實例就行。
其實static不光修飾類,還能用來修飾方法、修飾屬性等等,例如你們學習Java一開始就遇到的main方法,便爲static所修飾。當一個成員方法被static修飾以後,該方法就成爲靜態方法;當一個成員屬性被static修飾以後,該屬性就成爲靜態屬性。靜態方法和靜態屬性,它倆同嵌套類同樣不依賴於所在類的實例。外部若要訪問某個類的靜態方法,只需經過「類名.靜態方法名」便可;同理,經過「類名.靜態屬性名」就能訪問該類的靜態屬性。因爲靜態方法和靜態屬性擁有獨立調用的特性,所以它們經常出如今一些通用的工具場景,例如系統的數學函數庫Math,便提供了大量的靜態方法和靜態屬性。其中常見的靜態方法包括四捨五入函數Math.round、取絕對值函數math.abs、求平方根函數Math.sqrt等,常見的靜態屬性則有圓周率近似值Math.PI等。
那麼開發者本身定義一個新類,如何得知哪些屬性須要聲明爲靜態屬性,哪些方法須要聲明爲靜態方法呢?在多數狀況下,靜態屬性的取值通常要求是固定不變的,而靜態方法只容許對輸入參數進行加工,不容許操做其它的成員變量(靜態屬性除外)。以樹木類爲例,凡是會動態變化着的性狀與事情,顯然不適合聲明爲靜態成員;只有與生長過程無關的概念,才適合聲明爲靜態成員。譬如樹木可分爲喬木與灌木兩大類,可想而知喬木與灌木的類型取值,與每棵樹木的生長狀況沒有關聯,這兩種樹木類型就適合做爲靜態屬性。根據樹木的類型,推斷該樹木的類型名稱是「喬木」仍是「灌木」,這個類型名稱的判斷方法就適合做爲靜態方法。如此一來,Tree類即可添加下面的靜態成員聲明代碼:html
// static的字面意思是「靜態的」,意味着無需動態建立便可直接使用。 // 利用static修飾成員屬性,外部便可經過「類名.屬性名」直接訪問靜態屬性。 public static int TYPE_ARBOR = 1; public static int TYPE_BUSH = 2; // 利用static修飾成員方法,外部便可經過「類名.方法名」直接訪問靜態方法。 public static String getTypeName(int type) { String type_name = ""; if (type == TYPE_ARBOR) { type_name = "喬木"; } else if (type == TYPE_BUSH) { type_name = "灌木"; } return type_name; }
外部訪問樹木類的靜態成員,只要按照「類名.靜態成員名」的格式就好,具體的調用代碼以下所示:java
// 演示靜態成員的調用方式 private static void testStaticMember() { // 使用靜態屬性無需建立該類的實例,只要經過「類名.靜態屬性名」便可訪問靜態屬性 System.out.println("類型TYPE_ARBOR的取值爲"+TreeStatic.TYPE_ARBOR); System.out.println("類型TYPE_BUSH的取值爲"+TreeStatic.TYPE_BUSH); // 使用靜態方法無需建立該類的實例,只要經過「類名.靜態方法名」便可訪問靜態方法 String arbor_name = TreeStatic.getTypeName(TreeStatic.TYPE_ARBOR); System.out.println("類型TYPE_ARBOR對應的名稱是"+arbor_name); String bush_name = TreeStatic.getTypeName(TreeStatic.TYPE_BUSH); System.out.println("類型TYPE_BUSH對應的名稱是"+bush_name); }
神通廣大的static不只能夠修飾類、屬性、方法,它竟然還能修飾一段代碼塊!被static修飾的代碼段樣例以下:函數
static { // 這裏是被static修飾的代碼段內容 }
以上爲static所包裹的代碼段,又被稱做「靜態代碼塊」,其做用是在系統加載該類之時當即執行這部分代碼。由於此處的代碼被static包括,因此靜態代碼塊內部只能操做同類的靜態屬性和靜態方法,而不能操做普通的成員屬性和成員方法。但是這裏有個問題,早先提到構造方法纔是建立實例之時的初始操做,那麼靜態代碼塊與構造方法比起來,它們的執行順序孰先孰後?假若從Java的運行機制來解答該問題,不但費口舌並且傷腦筋,都說實踐出真知,接下來不如作個實驗,看看它們到底是怎樣的先來後到。
首先在樹木類中聲明一個靜態的整型變量leaf_count,之因此添加static修飾符,是由於要給靜態代碼塊使用;接着在靜態代碼塊內部對該變量作自增操做,並將變量值打印到日誌;同時在樹木類的構造方法裏面也進行leaf_count的自增運算,以及往控制檯輸出它的變量值。修改後的相關代碼片斷示例以下:工具
// 葉子數量,用來演示構造方法與初始靜態代碼塊的執行順序 public static int leaf_count = 0; // static還能用來包裹某個代碼塊,一旦當前類加載進內存,靜態代碼塊就當即執行 static { leaf_count++; System.out.println("這裏是初始的靜態代碼塊,此時葉子數量爲"+leaf_count); } public TreeStatic(String tree_name) { this.tree_name = tree_name; leaf_count++; System.out.println("這裏是構造方法,此時葉子數量爲"+leaf_count); }
最後回到外部建立該樹木類的新實例,對應代碼以下所示:學習
// 演示靜態代碼塊與構造方法的執行順序 private static void testStaticBlock() { System.out.println("開始建立樹木類的實例"); TreeStatic tree = new TreeStatic("月桂"); System.out.println("結束建立樹木類的實例"); }
運行以上的演示代碼,觀察到下列的日誌信息:測試
這裏是初始的靜態代碼塊,此時葉子數量爲1 開始建立樹木類的實例 這裏是構造方法,此時葉子數量爲2 結束建立樹木類的實例
從日誌結果可見,靜態代碼塊的內部代碼早早就獲得執行了,而構造方法的內部代碼要等到外部調用new的時候纔會執行,這證實了靜態代碼塊的執行時機確實先於該類的構造方法。this
靜態修飾符一邊給開發者帶來了便利,一邊也帶來了不大不小的困惑。爲了說明問題的迷惑性,接下來照例作個代碼實驗。仍舊在樹木類中先聲明一個靜態的整型變量annual_ring,再補充一個成員方法grow,該方法內部對annual_ring自增的同時也打印日誌。依據上述步驟給樹木類新增了以下代碼:設計
// 樹木年輪,用來演示靜態屬性的持久性 public static int annual_ring = 0; // 注意每次讀取靜態屬性,獲得的都是該屬性最近一次的數值 public void grow() { annual_ring++; System.out.println(tree_name+"的樹齡爲"+annual_ring); }
而後其它地方前後建立這個樹木類的兩個實例,就像下面代碼示範的那樣:日誌
// 演示靜態屬性的持久性 private static void testStaticProperty() { TreeStatic bigTree = new TreeStatic("大樹"); bigTree.grow(); TreeStatic littleTree = new TreeStatic("小樹"); littleTree.grow(); }
繼續運行上面的測試代碼,發現打印的日誌以下:htm
這裏是構造方法,此時葉子數量爲3 大樹的樹齡爲1 這裏是構造方法,此時葉子數量爲4 小樹的樹齡爲2
雖然bigTree和littleTree是新建立的實例,可是從日誌結果看它們的annual_ring數值居然是遞增的,這可真是咄咄怪事,兩個實例分明都是經過new出來的呀!產生怪異現象的罪魁禍首,原來就是static這個始做俑者,凡是被static修飾的靜態變量,它在內存中佔據了一塊固定的區域,無論所在類被建立了多少個實例,每一個實例引用的靜態變量依然是最初分配的那個。因而後面建立的樹木實例littleTree,其內部的annual_ring與以前實例bigTree的annual_ring保持一致,無怪乎先後兩實例的annual_ring數值是依序遞增的了。
因而可知,靜態屬性老是保存最後一次的數值,假若它的取值每次都發生變化,即便建立新實例也得不到靜態屬性最初的數值。這種後果顯而易見違背了靜態變量的設計初衷,在多數時候,開發者定義一個靜態屬性,本來是想做爲取值不變的常量使用,而不但願它變來變去。對於此類用於常量定義的靜態屬性,能夠在static前頭再添加修飾符final,表示該屬性只容許賦值一次,從而避免了屢次賦值致使取值更改的尷尬。下面是聯合修飾final和static的屬性定義代碼例子:
// 若想靜態屬性始終如一保持不變,就得給該屬性添加final修飾符,表示終態屬性只能被賦值一次 public final static int FINAL_TYPE_ARBOR = 1; public final static int FINAL_TYPE_BUSH = 2;
更多Java技術文章參見《Java開發筆記(序)章節目錄》