天天看點

Java 進階 -- static 解析

1. ​

​static​

​ 關鍵字的用途

Think in java 中的解釋

  • 英文版

    ​​https://www.linuxtopia.org/online_books/programming_books/thinking_in_java/TIJ304_014.htm​​

  • 中文 PDF

    連結: ​​https://pan.baidu.com/s/12gkiaJrTubMsUOjaBSkbOw​​ 提取碼: iyvs

P119 中有解釋:

Java 進階 -- static 解析
Java 進階 -- static 解析

簡而言之,一句話來描述就是:

友善在沒有建立對象的情況下來進行調用(方法/變量)

很顯然,被 ​

​static​

​ 關鍵字修飾的方法或者變量不需要依賴于對象來進行通路,隻要類被加載了,就可以通過類名去進行通路。

​static​

​​ 可以用來修飾類的成員方法、類的成員變量,另外可以編寫 ​

​static​

​ 代碼塊來優化程式性能

1.1 ​

​static​

​ 方法

​static​

​​ 方法一般稱作靜态方法,由于靜态方法不依賴于任何對象就可以進行通路,是以對于靜态方法來說,是沒有​

​this​

​​ 的,因為它不依附于任何對象,既然都沒有對象,就談不上 ​

​this​

​了。并且由于這個特性,在靜态方法中不能通路類的非靜态成員變量和非靜态成員方法,因為非靜态成員方法/變量都是必須依賴具體的對象才能夠被調用。

但是要注意的是,雖然在靜态方法中不能通路非靜态成員方法和非靜态成員變量,但是在非靜态成員方法中是可以通路靜态成員方法/變量的。舉個簡單的例子:

Java 進階 -- static 解析

在上面的代碼中,由于 ​

​print2()​

​​ 方法是獨立于對象存在的,可以直接用過類名調用。假如說可以在靜态方法中通路非靜态方法/變量的話,那麼如果在 ​

​main()​

​ 方法中有下面一條語句:

  MyObject.print2();      

此時對象都沒有,​

​str2​

​​ 根本就不存在,是以就會産生沖突了。同樣對于方法也是一樣,由于你無法預知在 ​

​print1()​

​ 方法中是否通路了非靜态成員變量,是以也禁止在靜态成員方法中通路非靜态成員方法。

而對于非靜态成員方法,它通路靜态成員方法/變量顯然是毫無限制的。

是以,如果說想在不建立對象的情況下調用某個方法,就可以将這個方法設定為 ​

​static​

​​。我們最常見的 ​

​static​

​​方法就是 ​

​main​

​​ 方法,至于為什麼 ​

​main​

​​ 方法必須是static的,現在就很清楚了。因為程式在執行 ​

​main​

​ 方法的時候沒有建立任何對象,是以隻有通過類名來通路。

另外記住,關于構造器是否是static方法可參考

1.2 ​

​static​

​ 變量

​static​

​ 變量也稱作 靜态變量,靜态變量和非靜态變量的差別是:

  • 靜态變量被所有的對象所共享,在記憶體中隻有一個副本,它當且僅當在 類初次加載時會被初始化。
  • 而非靜态變量是對象所擁有的,在建立對象的時候被初始化,存在多個副本,各個對象擁有的副本互不影響。
  • ​static​

    ​ 成員變量的初始化順序按照定義的順序進行初始化。

1.3 ​

​static​

​ 變量

​static​

​ 關鍵字還有一個比較關鍵的作用就是 用來 形成靜态代碼塊以優化程式性能。​

​static​

​​ 塊可以置于類中的任何地方,類中可以有多個 ​

​static​

​ 塊。在類初次被加載的時候,會按照 ​

​static​

​​ 塊的順序來執行每個 ​

​static​

​ 塊,并且隻會執行一次。

為什麼說 ​

​static​

​ 塊可以用來優化程式性能,是因為它的特性: 隻會在類加載的時候執行一次。下面看個例子:

class Person{
    private Date birthDate;
     
    public Person(Date birthDate) {
        this.birthDate = birthDate;
    }
     
    boolean isBornBoomer() {
        Date startDate = Date.valueOf("1946");
        Date endDate = Date.valueOf("1964");
        return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
    }
}      

​isBornBoomer​

​​ 是用來這個人是否是1946-1964年出生的,而每次 ​

​isBornBoomer​

​​ 被調用的時候,都會生成 ​

​startDate​

​​ 和 ​

​birthDate​

​ 兩個對象,造成了空間浪費,如果改成這樣效率會更好:

class Person{
    private Date birthDate;
    private static Date startDate,endDate;
    static{
        startDate = Date.valueOf("1946");
        endDate = Date.valueOf("1964");
    }
     
    public Person(Date birthDate) {
        this.birthDate = birthDate;
    }
     
    boolean isBornBoomer() {
        return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
    }
}      

是以,很多時候會将一些隻需要進行一次的初始化操作都放在 ​

​static​

​ 代碼塊中進行。

2. ​

​static​

​ 關鍵字的誤區

2.1 ​

​static​

​ 會改變類中成員的通路權限嗎?

在Java中能夠影響到通路權限的隻有 ​

​private​

​​、​

​public​

​​、​

​protected​

​(包括包通路權限)這幾個關鍵字。看下面的例子就明白了:

Java 進階 -- static 解析

示錯誤"Person.age 不可視",這說明 ​

​static​

​ 關鍵字并不會改變變量和方法的通路權限。

2.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​

​。在這裡永遠要記住一點:靜态成員變量雖然獨立于對象,但是不代表不可以通過對象去通路,所有的靜态方法和靜态變量都可以通過對象通路(隻要通路權限足夠)

2.3 ​

​static​

​ 能作用于局部變量麼?

但是在Java中切記: static是不允許用來修飾局部變量。不要問為什麼,這是Java文法的規定。

具體原因可以參考這篇博文的讨論:​​http://www.debugease.com/j2se/178932.html​​

3. 常見的筆試面試題

3.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");
    }
}      

Result:

base static
test static
base constructor
test constructor      

至于為什麼是這個結果,我們先不讨論,先來想一下這段代碼具體的執行過程:

  1. 在執行開始,先要尋找到​

    ​main​

    ​​方法,因為​

    ​main​

    ​方法是程式的入口.
  2. 但是在執行​

    ​main​

    ​​ 方法之前,必須先加載​

    ​Test​

    ​​ 類,而在加載​

    ​Test​

    ​​ 類的時候發現​

    ​Test​

    ​​ 類繼承自​

    ​Base​

    ​​類,是以會轉去先加載​

    ​Base​

    ​​ 類,在加載​

    ​Base​

    ​​ 類的時候,發現有​

    ​static​

    ​​ 塊,便執行了​

    ​static​

    ​塊。
  3. 在​

    ​Base​

    ​​ 類加載完成之後,便繼續加載​

    ​Test​

    ​​ 類,然後發現​

    ​Test​

    ​​ 類中也有​

    ​static​

    ​​ 塊,便執行​

    ​static​

    ​​ 塊。在加載完所需的類之後,便開始執行​

    ​main​

    ​ 方法。
  4. 在​

    ​main​

    ​​ 方法中執行​

    ​new Test()​

    ​ 的時候會先調用父類的構造器,然後再調用自身的構造器。是以,便出現了上面的輸出結果。

3.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");
    }
}      

Result:

test static
myclass static
person static
person Test
test constructor
person MyClass
myclass constructor      

類似地,我們還是來想一下這段代碼的具體執行過程。

  1. 首先加載​

    ​Test​

    ​​ 類,是以會執行​

    ​Test​

    ​​ 類中的​

    ​static​

    ​ 塊。
  2. 接着執行​

    ​new MyClass()​

    ​​,而​

    ​MyClass​

    ​​ 類還沒有被加載,是以需要加載​

    ​MyClass​

    ​​ 類。在加載​

    ​MyClass​

    ​​ 類的時候,發現​

    ​MyClass​

    ​​ 類繼承自​

    ​Test​

    ​​ 類,但是由于​

    ​Test​

    ​​ 類已經被加載了,是以隻需要加載​

    ​MyClass​

    ​​ 類,那麼就會執行​

    ​MyClass​

    ​​ 類的中的​

    ​static塊​

    ​。
  3. 在加載完之後,就通過構造器來生成對象。而在生成對象的時候,必須先初始化父類的成員變量,是以會執行​

    ​Test​

    ​​ 中的​

    ​Person person = new Person()​

    ​。
  4. 而​

    ​Person​

    ​​ 類還沒有被加載過,是以會先加載​

    ​Person​

    ​​ 類并執行​

    ​Person​

    ​​ 類中的​

    ​static​

    ​ 塊,接着執行父類的構造器,完成了父類的初始化,然後就來初始化自身了。
  5. 是以會接着執行​

    ​MyClass​

    ​​ 中的​

    ​Person person = new Person()​

    ​​,最後執行​

    ​MyClass​

    ​ 的構造器。

參考連結

  • ​​Java中的static關鍵字解析​​
  • ​​https://www.jianshu.com/p/388174bf905c​​