1. 程式人生 > >Java技術——Java中的static關鍵字解析

Java技術——Java中的static關鍵字解析

0. 前言

staticJava中的重要的一個點。也是面試的時候經常被問到的點,如果理解不夠很容易給面試官語言基礎不紮實的印象。本文從static方法、static內部類、static變數、以及static程式碼塊四個角度分別解析static

1.  static方法

Java程式設計思想》裡有這麼一句話——“在static方法中不能呼叫非靜態方法,反過來是可以的。而且可以在沒有建立任何物件時,通過類本身來呼叫static方法。這實際上正是static方法的主要用途。”

靜態方法不依賴於任何物件、僅通過類名就可以進行訪問,當然前提是類被載入。而非靜態方法依賴具體的物件才能夠被呼叫。所以靜態方法中不能訪問類的非靜態成員方法

/變數如果通過類名呼叫靜態方法,而該方法內部有非靜態變數,此時物件都還沒有建立,就會產生錯誤)。

當然反過來,在非靜態成員方法中是可以訪問靜態成員方法/變數的

我們最常見的static方法就是main方法,另外還有,即使沒有顯示地宣告為static類的構造器實際上也是靜態方法。

還有就是需要注意,如果你沒必要訪問物件外部,那麼就把你的方法成為靜態方法,因為它會比例項方法更快的呼叫。

(例項方法為了實現多型需維護一個虛擬函式導向表)

2.  static內部類

這裡主要總結一下兩者的區別,順便提出在使用static內部類時需要注意的一些性質:

1靜態內部類不需要有外部類的引用,但非靜態內部類需要持有對外部類的引用。

這也是很多非靜態內部類經常預設Android Activity外部類的引用,從而間接導致記憶體洩漏的原因。

2)非靜態內部類能夠訪問外部類的靜態和非靜態成員,而靜態類不能訪問外部類的非靜態成員,它只能訪問外部類的靜態成員。這一點和上面static方法的性質類似。

3.  static變數

靜態變數被所有的物件所共享,在記憶體中只有一個副本,僅在類初次載入時會被初始化。

而非靜態變數是物件所擁有的,在建立物件的時候被初始化,存在多個副本,各個物件擁有的副本互不影響。

(靜態成員變數雖然獨立於物件,但是不代表不可以通過物件去訪問,所有的靜態方法和靜態變數都可以通過物件訪問。)

需要注意的是,不論是static

方法還是static變數,通過類名直接呼叫時,也會判斷該方法/變數是否被修飾為private,如果是,仍然是無法獲取到的,這說明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()被呼叫的時候,都會生成startDateendDate兩個物件,造成了空間浪費,使用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的構造器