@author:Tobin
水平有限,如有錯誤,望請斧正。
參考《Java核心技術卷-基礎知識第10版》
對https://www.baeldung.com/java-static的部分解答進行了翻譯。
本節討論static修飾符的使用。
static顧名思義有靜態的含義,通過幾個問答解釋static的奧義。
static在java中主要修飾variables, methods, blocks,和nested classes。
前言:static關鍵字解析
在java語言中,關鍵字static表明,特定的成員屬於類型本身,而不是那個類型的實例。換句話說,不管我們產生多少對象,它們只共享一個靜態成員。而且在沒有對象創建時,這個靜態成員就存在了,所以說靜態成員屬於類型本身。比如我們熟悉的Math.PI,Math.pow()方法。下圖展現了static Variables的存儲形式。靜態變量是存儲在metaspace中的,而創建的對象是放在堆空間中的(具體的內存管理機制,等到后續的文章再研究)。
Q1:static修飾基本類型域
這個在之前的關鍵字解析中提到了,如果一個域被聲明為static,這個域會被所有屬於這個類的實例所共享,當然這個域是可以修改值的。除非是被聲明為static final,此時靜態變量變為靜態常量。依然為所有對象共享,但是在第一次初始化后,不再允許修改,比如Math.PI。
從存儲角度來講,靜態變量是存放在JVM memory一個特定的pool中,JVM稱之為Metaspace。(在Java8之前,這個pool被稱為 Permanent Generation ,翻譯為永生代,現在被移除,用Metaspace代替)。
舉個例子。
我們現在建立一個car類,我們需要每次新建一個car對象時,總的車數輛就增加1。
public class Car {
private String name;
private String engine;
public static int numberOfCars;
public Car(String name, String engine) {
this.name = name;
this.engine = engine;
numberOfCars++;
}
}
現在每當初始化一個car對象,numberOfCars就會加1,所以下面的assertions結果為true。
@Test
public void whenNumberOfCarObjectsInitialized_thenStaticCounterIncreases() {
new Car("Jaguar", "V8");
new Car("Bugatti", "W16");
assertEquals(2, Car.numberOfCars);
}
使用static field的場景:
- 當變量獨立於對象的時候
- 當變量被所有對象所共享時
牢記關鍵點: - 因為static變量屬於類,因此我們可以直接使用類名訪問static變量,不需要對象引用。
- static變量只能在類級別(class level)使用,在方法內聲明static變量和在主函數中都是不被允許的。
- 盡管我們可以用對象實例去訪問static域,但是我們不應該這樣做,因為會使得我們無法分辨這個域是屬於實例的還是類的。
Q2:static修飾方法
靜態方法獨立於對象實例,被所有實例所共享。
舉個例子。
比如Math類的方法,Collections方法,StringUtils from Apache或者CollectionUtils from Spring framework都是靜態的。
使用static 方法的場景:
- 在獨立於對象的條件下,使用static變量或者其它static method。
- static在工具類和助手類中廣泛應用。
牢記關鍵點: - static方法在編譯時解析,因為無法被重寫。(重寫的底層實現不了解,理解不夠)。
- abstract methods 不能是靜態的。
- 靜態方法不能使用this或者super關鍵字,靜態方法是沒有this隱式參數的方法,使用對象去調用方法,其實處理顯式參數以外還包括this參數,this參數指向的是該對象本身。
- 注意4點
- 實例方法可以直接訪問實例變量和實例方法
- 實例方法可以直接訪問靜態變量和靜態方法
- 靜態方法可以直接方法靜態變量和靜態方法
- 靜態方法不可以直接訪問實例變量和實例方法,必須使用對象引用
Q3:static修飾代碼塊
如果想要初始化一個list對象,想要在沒有任何對象實例的情況下完成,必須對代碼塊聲明static。如果不聲明,則是在對象實例化的時候執行代碼塊。
public class StaticBlockDemo {
public static List<String> ranks = new LinkedList<>();
static {
ranks.add("Lieutenant");
ranks.add("Captain");
ranks.add("Major");
}
static {
ranks.add("Colonel");
ranks.add("General");
}
}
使用static block的場景:
- 如果實例化static變量不僅是簡單的賦值,還有其它額外的邏輯操作時。
- 如果初始化實例變量是容易出錯的,且需要異常處理機制的時候。
牢記關鍵點: - 一個類可以有多個static blocks。
- 靜態域和靜態類解析和執行的順序,就是和它們在類中書寫的順序一樣。
Q4:static類
主要應用於實現靜態內部類-單例模式(設計模式的一種,待研究,推薦博文https://blog.csdn.net/mnb65482/article/details/80458571),代碼如下。這里還可以看到靜態方法的使用,利用靜態方法來構造對象,在工廠方法中也有運用。
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
public static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
使用static class的場景:
- 靜態內部類可以保證封裝性。
- 代碼放在只有會使用它的類里,增加了可讀性和可維護性。
- 如果嵌套類不需要訪問任何它封閉類的實例成員,最好聲明它為static,稱為靜態嵌套類,這樣它就和外部類沒有耦合,不需要任何堆和棧的存儲資源。這里可以把內部類和內部方法聯合起來理解(我現在對它們的存儲方法還不了解,即靜態內部類是如何展現它的優勢的)。不過內部類如果是靜態的,是無法訪問外部類的實例變量的,外部的靜態變量可以訪問。
public class Car {
private int temp;
public static class f1
{
temp = 2;//出錯,temp非靜態,修改為private static int temp可訪問;
}
}
牢記關鍵點:
- 靜態嵌套類無法訪問外部類的任何非靜態成員,除非是有外部類的對象引用。
- 靜態嵌套類可以訪問外部(封閉)類的static成員,即使成員是private。
- Java編程規范不允許最頂層的類是static的,只有嵌套類才可以聲明為static。
注意幾個說法,內部類是非static的嵌套類,靜態嵌套類就等於靜態內部類,封閉類等於外部類。
后續研究:
靜態內部類;
單例模式。