static關鍵字的理解和誤區

原文轉載自網絡:面試季,Java中的static關鍵字解析

1. static 關鍵字的用途

在《Java 編程思想》P86 頁有這樣一段話:

“static 方法就是沒有 this 的方法。在 static 方法內部不能調用非靜態方法,反過來是可以的。而且可以在沒有創建任何對象的前提下,僅僅通過類本身來調用 static 方法。這實際上正是 static 方法的主要用途。”

這段話雖然只是說明了 static 方法的特殊之處,但是可以看出 static 關鍵字的基本作用,簡而言之,一句話來描述就是:

方便在沒有創建對象的情況下來進行調用(方法 / 變量)。

很顯然,被 static 關鍵字修飾的方法或者變量不需要依賴於對象來進行訪問,只要類被加載了,就可以通過類名去進行訪問。

static 可以用來修飾類的成員方法、類的成員變量,另外可以編寫 static 代碼塊來優化程序性能。

static 方法

static 方法一般稱作靜態方法,由於靜態方法不依賴於任何對象就可以進行訪問,因此對於靜態方法來說,是沒有this的,因爲它不依附於任何對象,既然都沒有對象,就談不上 this了。並且由於這個特性,在靜態方法中不能訪問類的非靜態成員變量和非靜態成員方法,因爲非靜態成員方法 / 變量都是必須依賴具體的對象才能夠被調用。

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

在上面的代碼中,由於 print2 方法是獨立於對象存在的,可以直接用過類名調用。假如說可以在靜態方法中訪問非靜態方法 / 變量的話,那麼如果在 main 方法中有下面一條語句:

MyObject.print2();

此時對象都沒有,str2 根本就不存在,所以就會產生矛盾了。同樣對於方法也是一樣,由於你無法預知在 print1 方法中是否訪問了非靜態成員變量,所以也禁止在靜態成員方法中訪問非靜態成員方法。

而對於非靜態成員方法,它訪問靜態成員方法 / 變量顯然是毫無限制的。

因此,如果說想在不創建對象的情況下調用某個方法,就可以將這個方法設置爲 static。我們最常見的 static 方法就是 main 方法,至於爲什麼 main 方法必須是 static 的,現在就很清楚了。因爲程序在執行 main 方法的時候沒有創建任何對象,因此只有通過類名來訪問。

另外記住,即使沒有顯示地聲明爲 static,類的構造器實際上也是靜態方法。

static 變量

static 變量也稱作靜態變量,靜態變量和非靜態變量的區別是:靜態變量被所有的對象所共享,在內存中只有一個副本,它當且僅當在類初次加載時會被初始化。而非靜態變量是對象所擁有的,在創建對象的時候被初始化,存在多個副本,各個對象擁有的副本互不影響。

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

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 被調用的時候,都會生成startDatebirthDate兩個對象,造成了空間浪費,如果改成這樣效率會更好:

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 關鍵字的誤區

1.static 關鍵字會改變類中成員的訪問權限嗎?

有些初學的朋友會將 java 中的 static 與 C/C++ 中的 static 關鍵字的功能混淆了。在這裏只需要記住一點:與 C/C++ 中的 static 不同,Java 中的 static 關鍵字不會影響到變量或者方法的作用域。在 Java 中能夠影響到訪問權限的只有 privatepublicprotected(包括包訪問權限)這幾個關鍵字。看下面的例子就明白了:

提示錯誤 "Person.age 不可視",這說明 static 關鍵字並不會改變變量和方法的訪問權限。

2. 能通過 this 訪問靜態成員變量嗎?

雖然對於靜態方法來說沒有 this,那麼在非靜態方法中能夠通過 this 訪問靜態成員變量嗎?先看下面的一個例子,這段代碼輸出的結果是什麼?

這裏面主要考察隊 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. 下面這段代碼的輸出結果是什麼?

輸出:

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. 這段代碼的輸出結果是什麼?

輸出:

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(),而 Person 類還沒有被加載過,因此會先加載 Person 類並執行 Person 類中的 static 塊。

接着執行Person類的構造器,完成了類的初始化,然後就來初始化自身了。因爲是先實例化Pserson類,再實例化自身(按照代碼的順序執行),因此會接着執行 MyClass 中的 Person person = new Person(),最後執行 MyClass 的構造器。

3. 這段代碼的輸出結果是什麼?

輸出:

test static 1
test static 2

雖然在 main 方法中沒有任何語句,但是還是會輸出,原因上面已經講述過了。另外,static 塊可以出現類中的任何地方(只要不是方法內部,記住,任何方法內部都不行),並且執行是按照 static 塊的順序執行的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章