static關鍵字
在類中,用static聲明的成員變量為靜態成員變量,也成為類變量。類變量的生命周期和類相同,在整個應用程序執行期間都有效。
這里要強調一下:
-
static修飾的成員變量和方法,從屬於類
-
普通變量和方法從屬於對象
-
靜態方法不能調用非靜態成員,編譯會報錯
一、static關鍵字的用途
一句話描述就是:方便在沒有創建對象的情況下進行調用(方法/變量)。
顯然,被static關鍵字修飾的方法或者變量不需要依賴於對象來進行訪問,只要類被加載了,就可以通過類名去進行訪問。
static可以用來修飾類的成員方法、類的成員變量,另外也可以編寫static代碼塊來優化程序性能
static方法
static方法也成為靜態方法,由於靜態方法不依賴於任何對象就可以直接訪問,因此對於靜態方法來說,是沒有this的,因為不依附於任何對象,既然都沒有對象,就談不上this了,並且由於此特性,在靜態方法中不能訪問類的非靜態成員變量和非靜態方法,因為非靜態成員變量和非靜態方法都必須依賴於具體的對象才能被調用。
雖然在靜態方法中不能訪問非靜態成員方法和非靜態成員變量,但是在非靜態成員方法中是可以訪問靜態成員方法和靜態成員變量。
代碼示例:
從上面代碼里看出:
-
靜態方法test2()中調用非靜態成員變量address,編譯失敗。這是因為,在編譯期並沒有對象生成,address變量根本就不存在。
-
靜態方法test2()中調用非靜態方法test1(),編譯失敗。這是因為,編譯器無法預知在非靜態成員方法test1()中是否訪問了非靜態成員變量,所以也禁止在靜態方法中調用非靜態成員方法
-
非靜態成員方法test1()訪問靜態成員方法test2()/變量name是沒有限制的
所以,如果想在不創建對象的情況下調用某個方法,就可以將這個方法設置為static。最常見的靜態方法就是main方法,這就是為什么main方法是靜態方法就一目了然了,因為程序在執行main方法的時候沒有創建任何對象,只有通過類名來訪問。
特別說明:static方法是屬於類的,非實例對象,在JVM加載類時,就已經存在內存中,不會被虛擬機GC回收掉,這樣內存負荷會很大,但是非static方法會在運行完畢后被虛擬機GC掉,減輕內存壓力
static變量
static變量也稱為靜態變量,靜態變量和非靜態變量的區別:
-
靜態變量被所有對象共享,在內存中只有一個副本,在類初次加載的時候才會初始化
-
非靜態變量是對象所擁有的,在創建對象的時候被初始化,存在多個副本,各個對象擁有的副本互不影響
static成員變量初始化順序按照定義的順序來進行初始化
static塊
構造方法用於對象的初始化。靜態初始化塊,用於類的初始化操作。
在靜態初始化塊中不能直接訪問非staic成員。
static塊的作用
靜態初始化塊的作用就是:提升程序性能。
為什么說靜態初始化塊能提升程序性能,代碼示例如下:
class Person{ private Date birthDate; public Person(Date birthDate) { this.birthDate = birthDate; } boolean isBornBoomer() { Date startDate = Date.valueOf("1946"); Date endDate = Date.valueOf("1964"); return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0; } }
isBornBoomer是用來這個人是否是1946-1964年出生的,而每次isBornBoomer被調用的時候,都會生成startDate和birthDate兩個對象,造成了空間浪費,如果改成這樣效率會更好:
class Person{
private Date birthDate; private static Date startDate,endDate; static{ startDate = Date.valueOf("1946"); endDate = Date.valueOf("1964"); } public Person(Date birthDate) { this.birthDate = birthDate; } boolean isBornBoomer() { return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0; } }
因此,很多時候會將一些只需要進行一次的初始化操作都放在static代碼塊中進行
靜態初始化塊可以置於類中的任何地方,類中可以有多個靜態初始化塊。
在類初次被加載時,會按照靜態初始化塊的順序來執行每個塊,並且只會執行一次。
二、static關鍵字的誤區
-
static關鍵字會改變類中成員的訪問權限嗎?
有些初學的朋友會將java中的static與C/C++中的static關鍵字的功能混淆了。在這里只需要記住一點:與C/C++中的static不同,Java中的static關鍵字不會影響到變量或者方法的作用域。在Java中能夠影響到訪問權限的只有private、public、protected(包括包訪問權限)這幾個關鍵字。看下面的例子就明白了:
1 public class Person { 2 3 public String name = "李四"; 4 5 private static String address = "中國"; 6 }
說明static關鍵字不能改變變量和方法的訪問權限
-
能通過this訪問靜態成員變量嗎?
1 public class Main { 2 static int value = 33; 3 4 public static void main(String[] args) throws Exception{ 5 new Main().printValue(); 6 } 7 8 private void printValue(){ 9 int value = 3; 10 System.out.println(this.value); 11 } 12 }
這段代碼輸出結果為:33
this代表什么?this代表當前對象,那么通過new Main()來調用printValue的話,當前對象就是通過new Main()生成的對象。而static變量是被對象所享有的,因此在printValue中的this.value的值毫無疑問是33。在printValue方法內部的value是局部變量,根本不可能與this關聯,所以輸出結果是33。在這里永遠要記住一點:靜態成員變量雖然獨立於對象,但是不代表不可以通過對象去訪問,所有的靜態方法和靜態變量都可以通過對象訪問(只要訪問權限足夠)。
-
static能作用於局部變量么?
static是不允許用來修飾局部變量。不要問為什么,這是Java語法的規定
三、static常見筆試面試題
1、下面這段代碼的輸出結果是什么?
1 public class Test extends Base{ 2 3 static{ 4 System.out.println("test static"); 5 } 6 7 public Test(){ 8 System.out.println("test constructor"); 9 } 10 11 public static void main(String[] args) { 12 new Test(); 13 } 14 } 15 16 class Base{ 17 18 static{ 19 System.out.println("base static"); 20 } 21 22 public Base(){ 23 System.out.println("base constructor"); 24 } 25 }
輸出結果為:
base static test static base constructor test constructor
分析下這段代碼的執行過程:
-
找到main方法入口,main方法是程序入口,但在執行main方法之前,要先加載Test類
-
加載Test類的時候,發現Test類繼承Base類,於是先去加載Base類
-
加載Base類的時候,發現Base類有static塊,而是先執行static塊,輸出base static結果
-
Base類加載完成后,再去加載Test類,發現Test類也有static塊,而是執行Test類中的static塊,輸出test static結果
-
Base類和Test類加載完成后,然后執行main方法中的
new Test()
,調用子類構造器之前會先調用父類構造器 -
調用父類構造器,輸出base constructor結果
-
然后再調用子類構造器,輸出test constructor結果
2、這段代碼的輸出結果是什么?
1 public class Test { 2 Person person = new Person("Test"); 3 static{ 4 System.out.println("test static"); 5 } 6 7 public Test() { 8 System.out.println("test constructor"); 9 } 10 11 public static void main(String[] args) { 12 new MyClass(); 13 } 14 } 15 16 class Person{ 17 static{ 18 System.out.println("person static"); 19 } 20 public Person(String str) { 21 System.out.println("person "+str); 22 } 23 } 24 25 26 class MyClass extends Test { 27 Person person = new Person("MyClass"); 28 static{ 29 System.out.println("myclass static"); 30 } 31 32 public MyClass() { 33 System.out.println("myclass constructor"); 34 } 35 }
輸出結果為:
test static myclass static person static person Test test constructor person MyClass myclass constructor
為什么輸出結果是這樣的?我們來分析下這段代碼的執行過程:
-
找到main方法入口,main方法是程序入口,但在執行main方法之前,要先加載Test類
-
加載Test類的時候,發現Test類有static塊,而是先執行static塊,輸出test static結果
-
然后執行
new MyClass()
,執行此代碼之前,先加載MyClass類,發現MyClass類繼承Test類,而是要先加載Test類,Test類之前已加載 -
加載MyClass類,發現MyClass類有static塊,而是先執行static塊,輸出myclass static結果
-
然后調用MyClass類的構造器生成對象,在生成對象前,需要先初始化父類Test的成員變量,而是執行
Person person = new Person("Test")
代碼,發現Person類沒有加載 -
加載Person類,發現Person類有static塊,而是先執行static塊,輸出person static結果
-
接着執行Person構造器,輸出person Test結果
-
然后調用父類Test構造器,輸出test constructor結果,這樣就完成了父類Test的初始化了
-
再初始化MyClass類成員變量,執行Person構造器,輸出person MyClass結果
-
最后調用MyClass類構造器,輸出myclass constructor結果,這樣就完成了MyClass類的初始化了
3、這段代碼的輸出結果是什么?
1 public class Test { 2 3 static{ 4 System.out.println("test static 1"); 5 } 6 public static void main(String[] args) { 7 8 } 9 10 static{ 11 System.out.println("test static 2"); 12 } 13 }
輸出結果為:
test static 1 test static 2
雖然在main方法中沒有任何語句,但是還是會輸出,原因上面已經講述過了。另外,static塊可以出現類中的任何地方(只要不是方法內部,記住,任何方法內部都不行),並且執行是按照static塊的順序執行的。