static的定義
方便在沒有創建對象的情況下來進行調用(方法/變量)。
很顯然,被static關鍵字修飾的方法或者變量不需要依賴於對象來進行訪問,只要類被加載了,就可以通過類名去進行訪問。
static可以用來修飾類的成員方法、類的成員變量,另外可以編寫static代碼塊來優化程序性能。
從JVM的類加載機制的角度講,靜態資源是類初始化的時候加載的,而非靜態資源是類new的時候加載的
靜態變量和靜態方法
static關鍵字最基本的用法是:
1、被static修飾的變量屬於類變量,可以通過類名.變量名直接引用,而不需要new出一個類來.
靜態變量和非靜態變量的區別是:靜態變量被所有的對象所共享,在內存中只有一個副本,它當且僅當在類初次加載時會被初始化。而非靜態變量是對象所擁有的,在創建對象的時候被初始化,存在多個副本,各個對象擁有的副本互不影響。
另外記住:static是不允許用來修飾局部變量。比如
上面會報錯的,這只是一個java的規范。
2、被static修飾的方法屬於類方法,可以通過類名.方法名直接引用,而不需要new出一個類來
另外記住,即使沒有顯示地聲明為static,類的構造器實際上也是靜態方法。
被static修飾的變量、被static修飾的方法統一屬於類的靜態資源,是類實例之間共享的,換言之,一處變、處處變。
JDK把不同的靜態資源放在了不同的類中而不把所有靜態資源放在一個類里面,很多人可能想當然認為當然要這么做,但是是否想過為什么要這么做呢?個人認為主要有三個好處:
1、不同的類有自己的靜態資源,這可以實現靜態資源分類。比如和數學相關的靜態資源放在java.lang.Math中,和日歷相關的靜態資源放在java.util.Calendar中,這樣就很清晰了
2、避免重名。不同的類之間有重名的靜態變量名、靜態方法名也是很正常的,如果所有的都放在一起不可避免的一個問題就是名字重復,這時候怎么辦?分類放置就好了。
3、避免靜態資源類無限膨脹,這很好理解。
這里總結一下靜態和非靜態之間的引用:
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就是靜態內部類。靜態內部類相關內容會在寫內部類的時候專門講到。
/* 下面程序演示如何在java中創建靜態內部類和非靜態內部類 */ class OuterClass{ private static String msg = "GeeksForGeeks"; // 靜態內部類 public static class NestedStaticClass{ // 靜態內部類只能訪問外部類的靜態成員 public void printMessage() { // 試着將msg改成非靜態的,這將導致編譯錯誤 System.out.println("Message from nested static class: " + msg); } } // 非靜態內部類 public class InnerClass{ // 不管是靜態方法還是非靜態方法都可以在非靜態內部類中訪問 public void display(){ System.out.println("Message from non-static nested class: "+ msg); } } }
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、降低了代碼的可讀性
建議在某些場景下導入特定的靜態資源,不建議使用“.*”的導入方式。
轉載:http://www.cnblogs.com/xrq730/p/4820992.html