Java中的static總結
static
修飾變數。在類載入的時候,初始化順序按照類定義的順序執行。也就是父類順序初始化—>子類順序初始化。只執行一次static
修飾程式碼塊。在類載入的時候,按照類定義的順序執行。也就是父類順序執行static塊—>子類順序執行static塊。只執行一次
static方法就是沒有this的方法。在static方法內部不能呼叫非靜態方法,反過來是可以的。而且可以在沒有建立任何物件的前提下,僅僅通過類本身來呼叫static方法。這實際上正是static方法的主要用途。 ——Java程式設計思想
讀完上面的引言後,這裡需要強調一點,在非靜態方法中,是可以通過this
來呼叫static
變數、方法的。
static
常見修飾
1 static
修飾方法
static
方法一般稱作靜態方法,由於靜態方法不依賴於任何物件就可以進行訪問,因此對於靜態方法來說,是沒有this
的,因為它不依附於任何物件,既然都沒有物件,就談不上this
了。並且由於這個特性,在靜態方法中不能訪問類的非靜態成員變數和非靜態成員方法,因為非靜態成員方法/變數都是須依賴具體的物件才能夠被呼叫。
雖然在靜態方法中不能訪問非靜態成員方法和非靜態成員變數,但是在非靜態成員方法中是可以訪問靜態成員方法/變數的。
class MyObject{ private static String str1 = "staticProperty"; private String str2 = "property"; public MyObject(){ } public void print1(){ System.out.println(str1); // 可以訪問靜態變數 System.out.println(str2); print2(); // 可以訪問靜態方法 } public static void print2(){ System.out.println(str1); System.out.println(str2); // 編譯出錯,不能訪問非靜態變數 print1(); // 編譯出錯,不能訪問非靜態方法 } }
在上面的程式碼中,由於print2
方法是獨立於物件存在的,可以直接用過類名呼叫。
因此,如果說想在不建立物件的情況下呼叫某個方法,就可以將這個方法設定為static。我們最常見的static
方法就是main
方法,至於為什麼main
方法必須是static
的,現在就很清楚了。因為程式在執行main方法的時候沒有建立任何物件,因此只有通過類名來訪問。
2 static
修飾成員變數
static
變數也稱作靜態變數,靜態變數和非靜態變數的區別是:
靜態變數被所有的物件所共享,在記憶體中只有一個副本,它當且僅當在類初次載入時會被初始化。而非靜態變數是物件所擁有的,在建立物件的時候被初始化,存在多個副本,各個物件擁有的副本互不影響。
static
成員變數的初始化順序按照定義的順序進行初始化。
3 static
修飾程式碼塊
比如JDBC
中通過靜態塊載入資源
static{
/**
* 6.解析資原始檔
*/
//6.1 獲取資原始檔解析器物件
ResourceBundle bundle=ResourceBundle.getBundle("DBOptions");
//6.2從資源獲取資料,填充四項變數
driver=bundle.getString("DRIVER");
url=bundle.getString("URL");
userName=bundle.getString("USERNAME");
password=bundle.getString("PASSWORD");
Class.forName(driver);
}
static
塊可以置於類中的任何地方,類中可以有多個static
塊。在類初次被載入的時候,會按照static
塊的順序來執行每個static
塊,並且只會執行一次。
4 static
修飾類
static
修飾類只有一種情況,那就是這個類屬於靜態內部類,接觸過Android
開發的話可能遇見過很多這樣的靜態內部類,如WindowManager.LayoutParams
類,LayoutParams
就是WindowManager
類下的靜態內部類,它的原始碼如下所示:
public interface WindowManager extends ViewManager {
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
//內部實現
}
}
static
誤區
1 static
關鍵字會改變類中成員的訪問許可權嗎?
有些初學的朋友會將Java
中的static
與C/C++
中的static
關鍵字的功能混淆了。在這裡只需要記住一點:與C/C++
中的static
不同,Java
中的static
關鍵字不會影響到變數或者方法的作用域。在Java
中能夠影響到訪問許可權的只有private、public、protected
(包括包訪問許可權)這幾個關鍵字。看下面的例子就明白了:
public class Main{
public static void main(String[] args){
System.out.println(Person.name);
System.out.println(Person.age); // 編譯出錯,欄位Person.age不可見
}
}
class Person{
public static String name = "小曾";
private static int age = 18;
}
提示錯誤Person.age
不可見,這說明static
關鍵字並不會改變變數和方法的訪問許可權。
2 能通過this
訪問靜態成員變數嗎?
本問題呼應開篇,雖然對於靜態方法來說沒有this
,那麼在非靜態方法中能夠通過this
訪問靜態成員變數嗎?先看下面的一個例子,這段程式碼輸出的結果是什麼?
public class Main {
static int value = 33;
public static void main(String[] args) throws Exception{
new Main().printValue();
}
private void printValue(){
int value = 3;
System.out.println(this.value);
}
}
// 列印結果:33
這裡面主要考察對this
和static
的理解。this
代表什麼?this
代表當前物件,那麼通過new Main()
來呼叫printValue
的話,當前物件就是通過new Main()
生成的物件。而static
變數是被物件所享有的,因此在printValue
中的this.value
的值毫無疑問是33。在printValue
方法內部的value
是區域性變數,根本不可能與this
關聯,所以輸出結果是33。在這裡永遠要記住一點:靜態成員變數雖然獨立於物件,但是不代表不可以通過物件去訪問,所有的靜態方法和靜態變數都可以通過物件訪問(只要訪問許可權足夠)。
3 static
能作用於區域性變數麼?
在C/C++
中static
是可以作用域區域性變數的,但是在Java
中切記:static
是不允許用來修飾區域性變數。不要問為什麼,這是Java
語法的規定。
常考static
相關題目
1 下面這段程式碼的輸出結果是什麼?
public class Test extends Base{
static{
System.out.println("test static");
}
public Test(){
System.out.println("test constructor");
}
public static void main(String[] args) {
new Test();
}
}
class Base{
static{
System.out.println("base static");
}
public Base(){
System.out.println("base constructor");
}
}
// 列印結果:
base static
test static
base constructor
test constructor
至於為什麼是這個結果,我們先不討論,先來想一下這段程式碼具體的執行過程,在執行開始,先要尋找到main
方法,因為main
方法是程式的入口,但是在執行main
方法之前,必須先載入Test
類,而在載入Test
類的時候發現Test
類繼承自Base
類,因此會轉去先載入Base
類,在載入Base
類的時候,發現有static
塊,便執行了static
塊。在Base
類載入完成之後,便繼續載入Test
類,然後發現Test
類中也有static
塊,便執行static
塊。在載入完所需的類之後,便開始執行main
方法。在main
方法中執行new Test()
的時候會先呼叫父類的構造器,然後再呼叫自身的構造器。因此,便出現了上面的輸出結果。
2 這段程式碼的輸出結果是什麼?
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");
}
}
// 列印結果:
test static
myclass static
person static
person Test
test constructor
person MyClass
myclass constructor
首先載入Test
類,因此會執行Test
類中的static
塊。接著執行new MyClass()
,而MyClass
類還沒有被載入,因此需要載入MyClass
類。在載入MyClass
類的時候,發現MyClass
類繼承自Test
類,但是由於Test
類已經被載入了,所以只需要載入MyClass
類,那麼就會執行MyClass
類的中的static
塊。在載入完之後,就通過構造器來生成物件。而在生成物件的時候,必須先初始化父類的成員變數,因此會執行Test
中的Person person = new Person("Test")
,而Person
類還沒有被載入過,因此會先載入Person
類並執行Person
類中的static
塊,接著再執行父類的構造器,完成了父類的初始化,然後就來初始化自身了,因此會接著執行MyClass
中的Person person = new Person("MyClass")
,最後執行MyClass
的構造器。
3 這段程式碼的輸出結果是什麼?
public class Test {
static{
System.out.println("test static 1");
}
public static void main(String[] args) {
}
static{
System.out.println("test static 2");
}
}
// 列印結果:
test static 1
test static 2
重要總結
經過以上的講解與案例後,我們總結出一個類啟動後的執行過程
- 載入類:通過
JVM
呼叫ClassLoader
類中的loaderClass()
方法載入我們要執行的類 - 執行
static
修飾的程式碼塊(從上往下執行,並且只執行一次) - 呼叫構造器
- 成員變數初始化(注意不是在建構函式中初始化)
- 執行構造方法(這時才是建構函式初始化)
- 物件建立完畢