static關鍵字做用

以前講到final關鍵字的做用是每次面試的時候我必問求職者的兩個問題之一,另一個問題就是文本會寫到的static。final和static同樣,都是一個小問題能夠看到一我的的基礎是否紮實以及平時是否有鑽研精神。java

靜態變量和靜態方法面試

static關鍵字最基本的用法是:函數

一、被static修飾的變量屬於類變量,能夠經過類名.變量名直接引用,而不須要new出一個類來spa

二、被static修飾的方法屬於類方法,能夠經過類名.方法名直接引用,而不須要new出一個類來線程

被static修飾的變量、被static修飾的方法統一屬於類的靜態資源,是類實例之間共享的,換言之,一處變、到處變。JDK把不一樣的靜態資源放在了不一樣的類中而不把全部靜態資源放在一個類裏面,不少人可能想固然認爲固然要這麼作,可是是否想過爲何要這麼作呢?我的認爲主要有三個好處:code

一、不一樣的類有本身的靜態資源,這能夠實現靜態資源分類。好比和數學相關的靜態資源放在java.lang.Math中,和日曆相關的靜態資源放在java.util.Calendar中,這樣就很清晰了blog

二、避免重名。不一樣的類之間有重名的靜態變量名、靜態方法名也是很正常的,若是全部的都放在一塊兒不可避免的一個問題就是名字重複,這時候怎麼辦?分類放置就行了。資源

三、避免靜態資源類無限膨脹,這很好理解。編譯器

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出來以後產生的,所以屬於類的 這些東西它都能認識。因此上面的幾個問題答案就很明確了:

一、靜態方法能不能引用非靜態資源?不能,new的時候纔會產生的東西,對於初始化後就存在的靜態資源來講,根本不認識它。

二、靜態方法裏面能不能引用靜態資源?能夠,由於都是類初始化的時候加載的,你們相互都認識。

三、非靜態方法裏面能不能引用靜態資源?能夠,非靜態方法就是實例方法,那是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,我的的態度是:

一、簡化了一些操做,好比靜態導入Math下的全部靜態資源,在頻繁使用Math類下靜態資源的地方,能夠少些不少「Math.」

二、下降了代碼的可讀性

建議在某些場景下導入特定的靜態資源,不建議使用「.*」的導入方式。

相關文章
相關標籤/搜索