0. 前言 java
static是Java中的重要的一個點。也是面試的時候常常被問到的點,若是理解不夠很容易給面試官語言基礎不紮實的印象。本文從static方法、static內部類、static變量、以及static代碼塊四個角度分別解析static關鍵字。轉載請註明出處爲SEU_Calvin的博客。面試
1. static方法編程
《Java編程思想》裏有這麼一句話——「static方法就是沒有this的方法。在static方法內部不能調用非靜態方法,反過來是能夠的。並且能夠在沒有建立任何對象的前提下,僅僅經過類自己來調用static方法。這實際上正是static方法的主要用途。」函數
static方法通常稱做靜態方法,因爲靜態方法不依賴於任何對象、僅經過類名就能夠進行訪問,前提是類被加載。也所以在靜態方法中不能訪問類的非靜態成員方法/變量,由於非靜態成員方法/變量都是必須依賴具體的對象纔可以被調用,若是經過類名調用靜態方法,而該方法內部有非靜態變量,此時對象都尚未建立,就會產生錯誤,所以Java設置了這樣的限制。固然反過來,在非靜態成員方法中是能夠訪問靜態成員方法/變量的。性能
咱們最多見的static方法就是main方法,另外還有,即便沒有顯示地聲明爲static,類的構造器實際上也是靜態方法。優化
還有就是須要注意,若是你不必訪問對象外部,那麼就把你的方法成爲靜態方法,由於它會比實例方法更快的調用(後者爲了實現多態需維護一個虛擬函數導向表)。ui
2. static內部類this
靜態內部類和非靜態內部類是咱們在開發中都常常用到的,那麼二者之間到底有什麼不一樣呢?spa
這裏主要總結一下二者的區別,順便提出在使用static內部類時須要注意的一些性質:.net
(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的構造器。