static關鍵字作用總結
轉自:https://www.cnblogs.com/xrq730/p/4820992.html
前言
之前講到final關鍵字的作用是每次面試的時候我必問求職者的兩個問題之一,另外一個問題就是文字會寫到的static。final和static一樣,都是一個小問題可以看到一個人的基礎是否紮實以及平時是否有鑽研精神。
靜態變數和靜態方法
static關鍵字最基本的用法是:
1、被static修飾的變數屬於類變數,可以通過類名.變數名直接引用,而不需要new出一個類來
2、被static修飾的方法屬於類方法,可以通過類名.方法名直接引用,而不需要new出一個類來
被static修飾的變數、被static修飾的方法統一屬於類的靜態資源,是類例項之間共享的,換言之,一處變、處處變
1、不同的類有自己的靜態資源,這可以實現靜態資源分類。比如和數學相關的靜態資源放在java.lang.Math中,和日曆相關的靜態資源放在java.util.Calendar中,這樣就很清晰了
2、避免重名。不同的類之間有重名的靜態變數名、靜態方法名也是很正常的,如果所有的都放在一起不可避免的一個問題就是名字重複,這時候怎麼辦?分類放置就好了。
3、避免靜態資源類無限膨脹,這很好理解。
OK,再微微深入一下,也是有些人容易混淆的一個問題:靜態方法能不能引用非靜態資源?靜態方法裡面能不能引用靜態資源?非靜態方法裡面能不能引用靜態資源?比如就以這段程式碼為例,是否有錯?
1 public class A 2 { 3 private int i = 1; 4 5 public static void main(String[] args) 6 { 7 i = 1; 8 } 9 }
當然有錯,在第7行的地方。不妨這麼思考這個問題:
靜態資源屬於類,但是是獨立於類存在的。從JVM的類載入機制的角度講,靜態資源是類初始化的時候載入的,而非靜態資源是類new的時候載入的。類的初始化早於類的new,比如Class.forName(“xxx”)方法,就是初始化了一個類,但是並沒有new它,只是載入這個類的靜態資源罷了。所以對於靜態資源來說,它是不可能知道一個類中有哪些非靜態資源的;但是對於非靜態資源來說就不一樣了,由於它是new出來之後產生的,因此屬於類的這些東西它都能認識。所以上面的幾個問題答案就很明確了:
1、靜態方法能不能引用非靜態資源?不能,new的時候才會產生的東西,對於初始化後就存在的靜態資源來說,根本不認識它。
2、靜態方法裡面能不能引用靜態資源?可以,因為都是類初始化的時候載入的,大家相互都認識。
3、非靜態方法裡面能不能引用靜態資源?可以,非靜態方法就是例項方法,那是new之後才產生的,那麼屬於類的內容它都認識。
靜態塊
靜態塊也是static的重要應用之一。也是用於初始化一個類的時候做操作用的,和靜態變數、靜態方法一樣,靜態塊裡面的程式碼只執行一次,且只在初始化類的時候執行。靜態塊很簡單,不過提三個小細節:
1 public class A 2 { 3 private static int a = B(); 4 5 static 6 { 7 System.out.println("Enter A.static block"); 8 } 9 10 public static void main(String[] args) 11 { 12 new A(); 13 } 14 15 public static int B() 16 { 17 System.out.println("Enter A.B()"); 18 return 1; 19 } 20 }
列印結果是:
Enter A.B() Enter A.static block
得出第一個結論:靜態資源的載入順序是嚴格按照靜態資源的定義順序來載入的。這和周志明老師《深入理解Java虛擬機器:JVM高階特性與最佳實踐》中類初始化中的說法“<clinit>()方法是由編譯器自動收集類中所有類變數的賦值動作和靜態語句塊(static{}塊)中的語句合併產生的,編譯器收集的順序是由語句在原始檔中出現的順序所決定的”是一致的。
再看一個例子:
1 public class A 2 { 3 static 4 { 5 c = 3; 6 System.out.println(c); 7 } 8 9 private static int c; 10 }
這段程式碼的第6行是有錯誤的“Cannot reference a field before it is defined”。從這個例子得出第二個結論:靜態程式碼塊對於定義在它之後的靜態變數,可以賦值,但是不能訪問。
最後一個小例子:
1 public class A 2 { 3 static 4 { 5 System.out.println("A.static block"); 6 } 7 8 public A() 9 { 10 System.out.println("A.constructor()"); 11 } 12 }
1 public class B extends A 2 { 3 static 4 { 5 System.out.println("B.static block"); 6 } 7 8 public B() 9 { 10 System.out.println("B.constructor()"); 11 } 12 13 public static void main(String[] args) 14 { 15 new B(); 16 new B(); 17 } 18 }
結果是
A.static block B.static block A.constructor() B.constructor() A.constructor() B.constructor()
這個例子得出第三個結論:靜態程式碼塊是嚴格按照父類靜態程式碼塊->子類靜態程式碼塊的順序載入的,且只加載一次。
static修飾類
這個用得相對比前面的用法少多了,static一般情況下來說是不可以修飾類的,如果static要修飾一個類,說明這個類是一個靜態內部類(注意static只能修飾一個內部類),也就是匿名內部類。像執行緒池ThreadPoolExecutor中的四種拒絕機制CallerRunsPolicy、AbortPolicy、DiscardPolicy、DiscardOldestPolicy就是靜態內部類。靜態內部類相關內容會在寫內部類的時候專門講到。
import static
這個比較冷門,基本很少看見有地方用,使用JUnit可能會用到,寫assert的時候會方便些。import static是JDK1.5之後的新特性,這兩個關鍵字連用可以指定匯入某個類中的指定靜態資源,並且不需要使用類名.資源名,可以直接使用資源名。注意一下,import static必須這麼寫,而不能寫成static import。舉個例子來看一下:
1 import static java.lang.Math.*; 2 3 public class A 4 { 5 public static void main(String[] args) 6 { 7 System.out.println(sin(2.2)); 8 } 9 }
這麼寫意味著我匯入了Math下的所有靜態資源,main函式裡面我就可以直接用sin(2.2)而不需要使用Math.sin(2.2)了。注意一下,要寫import static java.lang.Math.*,最後的“.*”不可少,有了這兩個字元才意味著匯入的是Math下的所有靜態資源,寫成import static java.lang.Math是有問題的。當然,我們也可以指定只匯入某個靜態資源,比如只匯入Math下sin這個方法而不匯入Math下的所有靜態資源:
1 import static java.lang.Math.sin; 2 3 public class A 4 { 5 public static void main(String[] args) 6 { 7 System.out.println(sin(2.2)); 8 } 9 }
這麼寫也是沒問題的。匯入靜態變數也是一樣,有興趣的可以自己試一下。對於import static,個人的態度是:
1、簡化了一些操作,比如靜態匯入Math下的所有靜態資源,在頻繁使用Math類下靜態資源的地方,可以少些很多“Math.”
2、降低了程式碼的可讀性
建議在某些場景下匯入特定的靜態資源,不建議使用“.*”的匯入方式。