0. 前言
static是java中的重要的一個點。也是面試的時候經常被問到的點,如果理解不夠很容易給面試官語言基礎不扎實的印象。本文從static方法、static內部類、static變量、以及static代碼塊四個角度分別解析static關鍵字。轉載請注明出處為SEU_Calvin的博客。
1. static方法
《Java編程思想》里有這么一句話——“static方法就是沒有this的方法。在static方法內部不能調用非靜態方法,反過來是可以的。而且可以在沒有創建任何對象的前提下,僅僅通過類本身來調用static方法。這實際上正是static方法的主要用途。”
static方法一般稱作靜態方法,由于靜態方法不依賴于任何對象、僅通過類名就可以進行訪問,前提是類被加載。也因此在靜態方法中不能訪問類的非靜態成員方法/變量,因為非靜態成員方法/變量都是必須依賴具體的對象才能夠被調用,如果通過類名調用靜態方法,而該方法內部有非靜態變量,此時對象都還沒有創建,就會產生錯誤,因此Java設置了這樣的限制。當然反過來,在非靜態成員方法中是可以訪問靜態成員方法/變量的。
我們最常見的static方法就是main方法,另外還有,即使沒有顯示地聲明為static,類的構造器實際上也是靜態方法。
還有就是需要注意,如果你沒必要訪問對象外部,那么就把你的方法成為靜態方法,因為它會比實例方法更快的調用(后者為了實現多態需維護一個虛擬函數導向表)。
2. static內部類
靜態內部類和非靜態內部類是我們在開發中都經常用到的,那么兩者之間到底有什么不同呢?
這里主要總結一下兩者的區別,順便提出在使用static內部類時需要注意的一些性質:
(1)內部靜態類不需要有指向外部類的引用,但非靜態內部類需要持有對外部類的引用。這也是很多非靜態內部類經常默認Android Activity外部類的引用,從而間接導致內存泄漏的原因。
(2)非靜態內部類能夠訪問外部類的靜態和非靜態成員,顯然一個非靜態內部類不能脫離外部類實體被創建,而靜態類不能訪問外部類的非靜態成員,它只能訪問外部類的靜態成員。這一點和上面static方法的性質類似。
3. static變量
同樣介紹靜態變量和非靜態變量的區別:
靜態變量被所有的對象所共享,在內存中只有一個副本,它當且僅當在類初次加載時會被初始化。而非靜態變量是對象所擁有的,在創建對象的時候被初始化,存在多個副本,各個對象擁有的副本互不影響。靜態成員變量雖然獨立于對象,但是不代表不可以通過對象去訪問,所有的靜態方法和靜態變量都可以通過對象訪問。
需要注意的是,不論是static方法還是static變量,通過類名直接調用時,也會判斷該方法/變量是否被修飾為PRivate,如果是,仍然是無法獲取到的,這說明static關鍵字無法改變成員的訪問權限。
4. static代碼塊
首先看看下面程序會輸出什么呢?
public class Test { static{ System.out.println("test static 1");} public static void main(String[] args) {} static{ System.out.println("test static 2");}}雖然在main方法中沒有任何語句,但是還是會輸出"test static 1"、"test static 2",static塊可以置于類中的任何地方,只要不是方法內部,類中也可以有多個static塊。在類初次被加載的時候,會按照static塊的順序來執行每個static塊,并且只會執行一次。
根據只會執行一次的特性,靜態代碼塊可以用以優化程序性能。實例如下:
class Person{ private Date birthDate; public Person(Date birthDate) { this.birthDate = birthDate; } boolean isBirthdaySuitable() { Date startDate = Date.valueOf("1990"); Date endDate = Date.valueOf("1999"); return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) <= 0; }}這個實例用于判斷該Person是否是90后孤寡老人。每次isBirthdaySuitable()被調用的時候,都會生成startDate和endDate兩個對象,造成了空間浪費,使用static靜態塊優化如下:
class Person{ private Date birthDate;private static Date startDate,endDate;//一次性的初始化操作放在static代碼塊中進行 static{ startDate = Date.valueOf("1990"); endDate = Date.valueOf("1999"); } public Person(Date birthDate) { this.birthDate = birthDate; } boolean isBirthdaySuitable () { return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) <=0; }}5. static代碼塊的執行順序
先看看下面程序會輸出什么?
public class Test { Person person = new Person("Test"); static{ System.out.println("test static"); } public Test() { System.out.println("test constructor"); } public static void main(String[] args) { new MyClass(); }} class Person{ static{ System.out.println("person static"); } public Person(String str) { System.out.println("person "+str); }} class MyClass extends Test { Person person = new Person("MyClass"); static{ System.out.println("myclass static"); } public MyClass() { System.out.println("myclass constructor"); }}我們來分析一下這段代碼的執行過程:
(1)首先加載Test類,因此會先執行Test類中的static塊。
(2)接著執行main函數中的newMyClass(),而MyClass類還沒有被加載,因此需要加載MyClass類。在加載MyClass類的時候,發現MyClass類繼承自Test類,但是由于Test類已經被加載過了,所以只需要加載MyClass類,那么就會執行MyClass類的中的static塊。
(3)在加載完之后,就通過構造器來生成對象。而在生成對象的時候,必須先初始化父類的成員變量,因此會執行Test中的Personperson = new Person(),而Person類還沒有被加載,因此會先加載Person類并執行Person類中的static塊,接著執行父類的構造器,完成了父類的初始化。
(4)最后初始化MyClass,因此會先接著執行MyClass中的Person person = new Person(),最后執行MyClass的構造器。
新聞熱點
疑難解答