Java Static關(guān)鍵字詳解

  提起static關(guān)鍵字,相信大家絕對不會陌生,但是,想要完全說明白,猛的一想,發(fā)現(xiàn)自己好像又說不太明白... ...比方說,昨天被一個同學(xué)問起的時候... ... 當(dāng)然,不是所有人都像我一樣學(xué)藝不精的,但是像這樣的基礎(chǔ)不牢的人應(yīng)該不少,因為常用,所以用大家都會,但是談到精細(xì)之處都夠嗆。這篇博客是我翻出我原來的學(xué)習(xí)筆記再加上自己看的一些博客整理出來的,供基礎(chǔ)知識不怎么牢靠的同學(xué)參考參考。

1. static 關(guān)鍵字要解決的問題

  這里摘錄一下《Java編程思想(第四版)》里關(guān)于static關(guān)鍵字的一段原話:(P29)通常來說,當(dāng)創(chuàng)建類時,就是在描述那個類的對象的外觀與行為。除非用new創(chuàng)建那個對象,否則,實際上并未獲得任何對象。執(zhí)行new來創(chuàng)建對象的時候,數(shù)據(jù)存儲空間才被分配,其方法才供外界調(diào)用。有兩種情形用上述方法是無法解決的。一種情形是,只想為某特定域分配單一存儲空間,而不去考慮究竟要創(chuàng)建多少個對象,甚至根本不需要創(chuàng)建任何對象。另一種情形是,希望某個方法不與包含他的類的任何對象關(guān)聯(lián)在一起。也就是說,即使沒有創(chuàng)建對象,也能夠調(diào)用方法。簡單來說,static的主要目的就是創(chuàng)建獨立于具體對象的域變量與方法。

2. static修飾的變量或方法或類的加載時機(jī)

  在加載類的同時加在static修飾的部分。(注意:這個時候,還不存在具體對象,并且這個過程只進(jìn)行一次

3. 通過代碼示例來分別看看靜態(tài)變量、靜態(tài)方法、靜態(tài)類的效果

3.1 靜態(tài)變量

萬碼學(xué)堂,電腦培訓(xùn),計算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

public class StaticTest{    public static int count =0;
    
    @SuppressWarnings("static-access")    public static void main(String[] args) {        // TODO Auto-generated method stub
        StaticTest test1 = new StaticTest();
        System.out.println(test1.count);
        StaticTest test2 = new StaticTest();
        test2.count++;
        System.out.println(test1.count+" "+test2.count+" "+StaticTest.count);
    }

}

萬碼學(xué)堂,電腦培訓(xùn),計算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

輸出結(jié)果:

0
1    1    1

可見,static變量并不是所在類的某個具體對象所有,而是該類的所有對象所共有的,靜態(tài)變量既能被對象調(diào)用,也能直接拿類來調(diào)用。

除此之外,靜態(tài)變量不能引用非靜態(tài)方法,原因正如前面描述靜態(tài)加載時機(jī)中說的那樣,加載靜態(tài)的時候,非靜態(tài)的變量、方法等還不存在,當(dāng)然就無法引用了。但是,非靜態(tài)方法或類卻能正常引用靜態(tài)變量或方法。因為非靜態(tài)總是在靜態(tài)之后出現(xiàn)的。

3.2 靜態(tài)方法

  靜態(tài)方法和靜態(tài)變量一樣,屬于類所有,在類加載的同時執(zhí)行,不屬于某個具體的對象,所有對象均能調(diào)用。對于靜態(tài)方法需要注意以下幾點:

  • 它們僅能調(diào)用其他的static 方法。

  • 它們只能訪問static數(shù)據(jù)。

  • 它們不能以任何方式引用this 或super。

萬碼學(xué)堂,電腦培訓(xùn),計算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

class Simple {    static void go() {
       System.out.println("Welcome");
    }
} 
public class Cal {    public static void main(String[] args) {
       Simple.go();
    }
}

萬碼學(xué)堂,電腦培訓(xùn),計算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

 

  靜態(tài)方法一般用于工具類中,可以直接拿類名調(diào)用工具方法進(jìn)行使用。

3.3 靜態(tài)類

  一般來說,一個普通類是不允許被聲明為static的,但是,在內(nèi)部類中可以將其聲明為static的,這個時候,外部類可以直接調(diào)用內(nèi)部類,因為static的內(nèi)部類是在加載外部類的同時加載的,所以也就是說,并不要實例化外部類就能直接調(diào)用靜態(tài)內(nèi)部類。看例子:

萬碼學(xué)堂,電腦培訓(xùn),計算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

public class BaseStatic {
  static {
        System.out.println("Load base static");
    }    public BaseStatic(){
        System.out.println("BaseStatic constructor");
    }    
    static class BaseInnerClass{        static{
            System.out.println("Base inner class static");
        }        
        public BaseInnerClass(){
            System.out.println("BaseInnerClass constructor");
        }
    }

}public class StaticLoadOrderTest{    public static void main(String[] args) {        // TODO Auto-generated method stub
        new BaseStatic.BaseInnerClass();
    }

}

萬碼學(xué)堂,電腦培訓(xùn),計算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

在看答案之前,自己想想這個輸出結(jié)果是什么?

先不急著看答案,我們先來看看這個執(zhí)行過程:首先,在進(jìn)入StaticLoadOrderTest的main方法之前,加載StaticLoadOrderTest類,然后執(zhí)行new BaseStatic.BaseInnerClass();這里需要注意:因為BaseInnerClass是靜態(tài)的,所以這里并不需要加載外部類和實例化外部類,可以直接加載BaseInnerClass并實例化。所以輸出:

 Base inner class static

這里留個坑:當(dāng)直接使用外部類類名.靜態(tài)內(nèi)部類進(jìn)行實例化的時候,如果外部類沒有加載的話(實際上也是沒有加載),那么這個statement: BaseStatic.BaseInnerClass中的BaseStatic是個什么存在????難道只是與靜態(tài)內(nèi)部類發(fā)生了簡單的名稱關(guān)聯(lián)嗎?若是這樣還設(shè)計靜態(tài)內(nèi)部類干嘛呢?我覺得java設(shè)計者們不至于犯這種錯誤吧?也可能因為自己對于JVM并不熟悉,對于底層不太了解,若是路過的大神能幫忙解決一下,感激不盡?。。?!

3.4 關(guān)于靜態(tài)加載順序的示例

下面這段代碼的輸出是什么?

萬碼學(xué)堂,電腦培訓(xùn),計算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

public class BaseStatic {    static {
        System.out.println("Load base static");
    }    
    public BaseStatic(){
        System.out.println("BaseStatic constructor");
    }    
    static class BaseInnerClass{        static{
            System.out.println("Base inner class static");
        }        
        public BaseInnerClass(){
            System.out.println("BaseInnerClass constructor");
        }
    }

}public class StaticLoadOrderTest {    
    static {
        System.out.println("Load test");
    }    
    public StaticLoadOrderTest(){
        System.out.println("Test constructor");
    }    public static void main(String[] args) {        // TODO Auto-generated method stub
        new BaseStatic();        new StaticLoadOrderTest();        new BaseStatic.BaseInnerClass();
    }

}

萬碼學(xué)堂,電腦培訓(xùn),計算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

  和上面一樣,分析一下過程:在進(jìn)入main方法之前,需要加載StaticLoadOrderTest類,這時候發(fā)現(xiàn)有static代碼塊,先加載靜態(tài)代碼塊,然后進(jìn)入main方法內(nèi)部,new BaseStatic(),這時候需要加載BaseStatic類,同時也要先加載靜態(tài)代碼塊,然后調(diào)用構(gòu)造器。注意:這里并沒有加載BaseInnerClass,因為它是內(nèi)部類只有在真正用到的時候才會進(jìn)行加載,相信聰明的讀者看到這個是不是想到了又一種單例設(shè)計模式的實現(xiàn)方式?自己研究吧?;氐絤ain方法中,接下來該執(zhí)行new StaticLoadOrderTest()了,因為StaticLoadOrderTest類之前已經(jīng)被加載過一次了,并且類只加載一次,所以這里就直接構(gòu)造了;然后是最后一句new BaseStatic.BaseInnerClass()了,和上面例子一樣,這里就不再細(xì)講。所以輸出結(jié)果為:

Load test
Load base staticBaseStatic constructor
Test constructor
Base inner class staticBaseInnerClass constructor

  再考慮一下,如果我把上面的例子改成下面這樣,輸出結(jié)果又會是什么呢?

萬碼學(xué)堂,電腦培訓(xùn),計算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

 "Load base static""BaseStatic constructor" "Base inner class static""BaseInnerClass constructor"  StaticLoadOrderTest "Load test""Test constructor"

萬碼學(xué)堂,電腦培訓(xùn),計算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)