Java static關鍵字的理解
今天在學習一些jvm記憶體劃分相關知識的時候涉及到了一些靜態變數的知識,回憶起來總覺得不夠系統,所以又複習總結了一下Java中static關鍵字的相關知識點。static關鍵字作為Java基礎知識中比較難理解的一個點,一直學習得不夠深刻,也比較容易遺忘,這篇部落格會盡量詳盡地歸納相關知識。
1. static的作用
1.1 修飾方法
static方法就是沒有this的方法。在static方法內部不能呼叫非靜態方法,反過來是可以的。而且可以在沒有建立任何物件的前提下,僅僅通過類本身來呼叫static方法。這實際上正是static方法的主要用途。 ——《Java程式設計思想》P86
這句話說明了static修飾的方法(靜態方法),可以通過 類名.方法名
由此也可以聯想到,為什麼所有的main方法必須要用static修飾,就是因為main方法作為程式執行的入口,再被jvm呼叫的時候根本沒有事先例項化出來類的物件。
此外,在靜態方法中,只能呼叫靜態方法和靜態成員變數,不允許呼叫非靜態方法和非靜態成員變數。
public class Test { void printMethod(){ System.out.println(1); } static void staticMethod(){ printMethod(); //語法報錯:Cannot make a static reference to the non-static method printMethod() from the type Test } public static void main(String args[]){ Test.staticMethod(); } }
上面的程式碼中,在main方法中呼叫了靜態方法staticMethod(),而靜態方法中呼叫了非靜態方法printMethod(),整個過程中沒有Test的例項物件產生,而我們知道非靜態方法的呼叫必須依賴於例項物件,產生了矛盾,所以Java不允許在靜態方法中呼叫非靜態方法。成員變數亦是如此,非靜態成員變數作為例項物件的屬性,自然也不可能在沒有物件產生的情況下進行呼叫。
1.2 修飾變數 用static關鍵字修飾的變數(靜態變數)與非靜態變數的區別是:靜態變數在該類首次被載入的時候被初始化(jdk8以前存放在方法區(永久代),jdk8之後元空間代替了永久代,靜態變數存放在堆中),為該類所有的例項物件所共享,記憶體中僅存在一份副本,而非靜態變數則是具體的例項物件的屬性,在物件被建立的時候初始化,每個物件獨享一份副本且互不影響。
public class Test {
//靜態成員變數
static String staticStr = "this is a static string";
//非靜態成員變數
String nomalStr = "this is a nomal string";
public static void main(String[] args){
System.out.println(nomalStr); //報錯:Cannot make a static reference to the non-static field nomalStr
System.out.println(staticStr);
}
}
有趣的是,我們由此可以從另一個角度解釋為什麼靜態方法中不能呼叫靜態成員變數,例如上面的程式碼,由於靜態方法main()的執行不需要依賴於物件的建立,而沒有物件的建立jvm也不會去初始化非靜態變數,故無法呼叫。
public class Test {
static int num =1;
//不同的物件共享靜態成員變數
public static void main(String[] args){
Test t1 = new Test();
System.out.println(++t1.num); //2
Test t2 = new Test();
System.out.println(t2.num); //2
}
}
1.3 static程式碼塊 static比較進階的作用就是用來修飾程式碼塊了,static程式碼塊的特點是隻會在類被類載入器載入的時候執行一次,而在初始化和建立物件的時候都不會再執行,適當使用static程式碼塊可以減少jvm記憶體的佔用。
public class Test {
void isPass(int score){
//及格線
int passLevel = 60;
System.out.println(score >= passLevel ? "及格" : "不及格");
}
public static void main(String[] args){
Test t1 = new Test();
t1.isPass(90); //第一次呼叫會產生一個變數passLevel
Test t2 = new Test();
t2.isPass(59); //第二次呼叫會再次產生一個變數passLevel
}
}
變數 passLevel 在兩次呼叫中初始化了兩次,儲存在Java虛擬機器棧對應棧幀的區域性變量表中,造成資源的冗餘。不如改成:
public class Test {
//及格線
static int passLevel;
static {
passLevel = 60; //類載入的時候初始化一次
}
void isPass(int score){
System.out.println(score >= passLevel ? "及格" : "不及格");
}
public static void main(String[] args){
Test t1 = new Test();
t1.isPass(90);
Test t2 = new Test();
t2.isPass(59);
}
}
2. 歸納與注意 2.1 static修飾的成員變數或者方法對其訪問許可權並不產生任何影響。 2.2 在方法內部不允許定義靜態變數 2.3 靜態變數全域性共享,僅在類載入的時候初始化一次 2.4 靜態變數可以通過類名直接呼叫,也可以通過物件呼叫 2.5 靜態方法中不允許呼叫非靜態變數和方法,也不能使用this和super關鍵字