面試官:兄弟,說說Java的static關鍵字吧
讀者乙在上一篇我去系列文章裡留言說,“我盲猜下一篇標題是,‘我去,你竟然不知道 static 關鍵字’”。我只能說乙猜對了一半,像我這麼有才華的博主,怎麼可能被讀者猜中了心思呢,必須搞點不一樣的啊,所以本篇文章的標題你看到了。
七年前,我從美女很多的蘇州回到美女也不少的洛陽,抱著一幅“從二線城市退居三線城市”的心態,投了不少簡歷,也“約談”了不少面試官,但僅有兩三個令我感到滿意。其中有一位叫老馬,至今還活在我的微信通訊錄裡。他當時扔了一個面試題把我砸懵了:“兄弟,說說 Java 的 static 關鍵字吧。”
我那時候二十三歲,正值青春年華,自認為所有的面試題都能對答如流,結果沒想到啊,被“刁難”了——原來洛陽這塊網際網路的荒漠也有技術專家啊。現在回想起來,臉上不自覺地泛起了羞愧的紅暈:主要是自己當時太菜了。
不管怎麼說,經過多年的努力,我現在的技術功底已經非常紮實了,有能力寫篇文章剖析一下 Java 的 static 關鍵字了——只要能給初學者一些參考,我就覺得非常滿足。
先來個提綱挈領(唉呀媽呀,成語區博主上線了)吧:
static 關鍵字可用於變數、方法、程式碼塊和內部類,表示某個特定的成員只屬於某個類本身,而不是該類的某個物件。
01、靜態變數
靜態變數也叫類變數,它屬於一個類,而不是這個類的物件。
public class Writer {
private String name;
private int age;
public static int countOfWriters;
public Writer(String name, int age) {
this.name = name;
this.age = age;
countOfWriters++;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
其中,countOfWriters 被稱為靜態變數,它有別於 name 和 age 這兩個成員變數,因為它前面多了一個修飾符 static
。
這意味著無論這個類被初始化多少次,靜態變數的值都會在所有類的物件中共享。
Writer w1 = new Writer("沉默王二",18);
Writer w2 = new Writer("沉默王三",16);
System.out.println(Writer.countOfWriters);
按照上面的邏輯,你應該能推理得出,countOfWriters 的值此時應該為 2 而不是 1。從記憶體的角度來看,靜態變數將會儲存在 Java 虛擬機器中一個名叫“Metaspace”(元空間,Java 8 之後)的特定池中。
靜態變數和成員變數有著很大的不同,成員變數的值屬於某個物件,不同的物件之間,值是不共享的;但靜態變數不是的,它可以用來統計物件的數量,因為它是共享的。就像上面例子中的 countOfWriters,建立一個物件的時候,它的值為 1,建立兩個物件的時候,它的值就為 2。
簡單小結一下:
1)由於靜態變數屬於一個類,所以不要通過物件引用來訪問,而應該直接通過類名來訪問;
2)不需要初始化類就可以訪問靜態變數。
public class WriterDemo {
public static void main(String[] args) {
System.out.println(Writer.countOfWriters); // 輸出 0
}
}
02、靜態方法
靜態方法也叫類方法,它和靜態變數類似,屬於一個類,而不是這個類的物件。
public static void setCountOfWriters(int countOfWriters) {
Writer.countOfWriters = countOfWriters;
}
setCountOfWriters()
就是一個靜態方法,它由 static 關鍵字修飾。
如果你用過 java.lang.Math 類或者 Apache 的一些工具類(比如說 StringUtils)的話,對靜態方法一定不會感動陌生。
Math 類的幾乎所有方法都是靜態的,可以直接通過類名來呼叫,不需要建立類的物件。
簡單小結一下:
1)Java 中的靜態方法在編譯時解析,因為靜態方法不能被重寫(方法重寫發生在執行時階段,為了多型)。
2)抽象方法不能是靜態的。
3)靜態方法不能使用 this 和 super 關鍵字。
4)成員方法可以直接訪問其他成員方法和成員變數。
5)成員方法也可以直接方法靜態方法和靜態變數。
6)靜態方法可以訪問所有其他靜態方法和靜態變數。
7)靜態方法無法直接訪問成員方法和成員變數。
03、靜態程式碼塊
靜態程式碼塊可以用來初始化靜態變數,儘管靜態方法也可以在宣告的時候直接初始化,但有些時候,我們需要多行程式碼來完成初始化。
public class StaticBlockDemo {
public static List<String> writes = new ArrayList<>();
static {
writes.add("沉默王二");
writes.add("沉默王三");
writes.add("沉默王四");
System.out.println("第一塊");
}
static {
writes.add("沉默王五");
writes.add("沉默王六");
System.out.println("第二塊");
}
}
writes 是一個靜態的 ArrayList,所以不太可能在宣告的時候完成初始化,因此需要在靜態程式碼塊中完成初始化。
簡單小結一下:
1)一個類可以有多個靜態程式碼塊。
2)靜態程式碼塊的解析和執行順序和它在類中的位置保持一致。為了驗證這個結論,可以在 StaticBlockDemo 類中加入空的 main 方法,執行完的結果如下所示:
第一塊
第二塊
04、靜態內部類
Java 允許我們在一個類中宣告一個內部類,它提供了一種令人信服的方式,允許我們只在一個地方使用一些變數,使程式碼更具有條理性和可讀性。
常見的內部類有四種,成員內部類、區域性內部類、匿名內部類和靜態內部類,限於篇幅原因,前三種不在我們本次文章的討論範圍,以後有機會再細說。
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
public static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
以上這段程式碼是不是特別熟悉,對,這就是建立單例的一種方式,第一次載入 Singleton 類時並不會初始化 instance,只有第一次呼叫 getInstance()
方法時 Java 虛擬機器才開始載入 SingletonHolder 並初始化 instance,這樣不僅能確保執行緒安全也能保證 Singleton 類的唯一性。不過,建立單例更優雅的一種方式是使用列舉。
簡單小結一下:
1)靜態內部類不能訪問外部類的所有成員變數。
2)靜態內部類可以訪問外部類的所有靜態變數,包括私有靜態變數。
3)外部類不能宣告為 static。
學到了吧?學到就是賺到。
我是沉默王二,一枚有趣的程式設計師。如果覺得文章對你有點幫助,請微信搜尋「 沉默王二 」第一時間閱讀,回覆【666】更有我為你精心準備的 500G 高清教學視訊(已分門別類)。
本文 GitHub 已經收錄,有大廠面試完整考點,歡迎 Star。
原創不易,莫要白票,請你為本文點個贊吧,這將是我寫作更多優質文章的最強動力。