Java技術——Java中的static關鍵字解析
0. 前言
static是Java中的重要的一個點。也是面試的時候經常被問到的點,如果理解不夠很容易給面試官語言基礎不紮實的印象。本文從static方法、static內部類、static變數、以及static程式碼塊四個角度分別解析static
1. static方法
《Java程式設計思想》裡有這麼一句話——“在static方法中不能呼叫非靜態方法,反過來是可以的。而且可以在沒有建立任何物件時,通過類本身來呼叫static方法。這實際上正是static方法的主要用途。”
靜態方法不依賴於任何物件、僅通過類名就可以進行訪問,當然前提是類被載入。而非靜態方法依賴具體的物件才能夠被呼叫。所以靜態方法中不能訪問類的非靜態成員方法
當然反過來,在非靜態成員方法中是可以訪問靜態成員方法/變數的。
我們最常見的static方法就是main方法,另外還有,即使沒有顯示地宣告為static,類的構造器實際上也是靜態方法。
還有就是需要注意,如果你沒必要訪問物件外部,那麼就把你的方法成為靜態方法,因為它會比例項方法更快的呼叫。
(例項方法為了實現多型需維護一個虛擬函式導向表)。
2. static內部類
這裡主要總結一下兩者的區別,順便提出在使用static內部類時需要注意的一些性質:
(1)靜態內部類不需要有外部類的引用,但非靜態內部類需要持有對外部類的引用。
(2)非靜態內部類能夠訪問外部類的靜態和非靜態成員,而靜態類不能訪問外部類的非靜態成員,它只能訪問外部類的靜態成員。這一點和上面static方法的性質類似。
3. static變數
靜態變數被所有的物件所共享,在記憶體中只有一個副本,僅在類初次載入時會被初始化。
而非靜態變數是物件所擁有的,在建立物件的時候被初始化,存在多個副本,各個物件擁有的副本互不影響。
(靜態成員變數雖然獨立於物件,但是不代表不可以通過物件去訪問,所有的靜態方法和靜態變數都可以通過物件訪問。)
需要注意的是,不論是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類中的static塊。
(2)接著執行main函式中的newMyClass(),而MyClass類還沒有被載入,因此需要載入MyClass類。
在載入MyClass類的時候,發現MyClass類繼承自Test類,但是由於Test類已經被載入過了,接著會執行MyClass類的中的static塊。
(3)在載入完之後,就通過構造器來生成物件。而在生成物件的時候,必須先初始化父類的成員變數,因此會執行Test中的Person person = new Person(),而Person類還沒有被載入,因此會先載入Person類並執行Person類中的static塊,接著執行父類的構造器,完成了父類的初始化。
(4)最後初始化MyClass,因此會先接著執行MyClass中的Person person = new Person(),最後執行MyClass的構造器。