十五、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) {
public int getAge() {
return age;
public void setAge(int 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。
十六、Java 枚舉
開門見山地說吧,enum(枚舉)是 Java 1.5 時引入的關鍵字,它表示一種特殊類型的類,預設繼承自 java.lang.Enum。
為了證明這一點,我們來建立一個枚舉 PlayerType:
public enum PlayerType {
TENNIS,
FOOTBALL,
BASKETBALL
}
兩個關鍵字帶一個類名,還有大括号,以及三個大寫的單詞,但沒看到繼承 Enum 類啊?别着急,心急吃不了熱豆腐啊。使用 JAD 檢視一下反編譯後的位元組碼,就一清二楚了。
public final class PlayerType extends Enum
{
public static PlayerType[] values()
{
return (PlayerType[])$VALUES.clone();
}
public static PlayerType valueOf(String name)
{
return (PlayerType)Enum.valueOf(com/cmower/baeldung/enum1/PlayerType, name);
}
private PlayerType(String s, int i)
{
super(s, i);
}
public static final PlayerType TENNIS;
public static final PlayerType FOOTBALL;
public static final PlayerType BASKETBALL;
private static final PlayerType $VALUES[];
static
{
TENNIS = new PlayerType("TENNIS", 0);
FOOTBALL = new PlayerType("FOOTBALL", 1);
BASKETBALL = new PlayerType("BASKETBALL", 2);
$VALUES = (new PlayerType[] {
TENNIS, FOOTBALL, BASKETBALL
});
}
}
看到沒?PlayerType 類是 final 的,并且繼承自 Enum 類。這些工作我們程式員沒做,編譯器幫我們悄悄地做了。此外,它還附帶幾個有用靜态方法,比如說 values() 和 valueOf(String name)。
01、内部枚舉
好的,小夥伴們應該已經清楚枚舉長什麼樣子了吧?既然枚舉是一種特殊的類,那它其實是可以定義在一個類的内部的,這樣它的作用域就可以限定于這個外部類中使用。
public class Player {
private PlayerType type;
public enum PlayerType {
TENNIS,
FOOTBALL,
BASKETBALL
}
public boolean isBasketballPlayer() {
return getType() == PlayerType.BASKETBALL;
}
public PlayerType getType() {
return type;
}
public void setType(PlayerType type) {
this.type = type;
}
}
PlayerType 就相當于 Player 的内部類,isBasketballPlayer() 方法用來判斷運動員是否是一個籃球運動員。
由于枚舉是 final 的,可以確定在 Java 虛拟機中僅有一個常量對象(可以參照反編譯後的靜态代碼塊「static 關鍵字帶大括号的那部分代碼」),是以我們可以很安全地使用“==”運算符來比較兩個枚舉是否相等,參照 isBasketballPlayer() 方法。
那為什麼不使用 equals() 方法判斷呢?
if(player.getType().equals(Player.PlayerType.BASKETBALL)){};
if(player.getType() == Player.PlayerType.BASKETBALL){};
1
2
“==”運算符比較的時候,如果兩個對象都為 null,并不會發生 NullPointerException,而 equals() 方法則會。
另外, “==”運算符會在編譯時進行檢查,如果兩側的類型不比對,會提示錯誤,而 equals() 方法則不會。