1. 程式人生 > >Java基礎:詳解static 關鍵字

Java基礎:詳解static 關鍵字

1. 前言

前文中說到了static關鍵字,在Java中這是一個很重要的關鍵字,它有很多的用法,並且在某些特定的情況下使用可以優化程式的效能。本文學習static關鍵字的應用場景。在這之前瞭解變數的型別,變數按作用域分為成員變數和區域性變數,成員變數也就是全域性變數,它是在類中宣告的,不屬於類中任何一個方法。而區域性變數是在類中的方法體中宣告的,作用域只是這個方法體。

2. 詳解static關鍵字

方便在沒有建立物件的情況下來進行呼叫(方法/變數)。

static關鍵字可以用來修飾類的成員方法、類的成員變數,另外可以編寫static程式碼塊來優化程式效能。

  • static關鍵字修飾的類成員變數在JVM記憶體模型中,儲存在方法區的類成員變數(靜態變數)
  • static關鍵字修飾的類成員方法在JVM記憶體模型中,儲存在方法區的方法資訊中。一旦類的靜態方法被呼叫,則會在虛擬機器棧中建立一個棧幀,然後棧幀被壓入虛擬機器棧。

如下面一幅圖可以理解static關鍵字修飾的成員變數和成員方法在JVM記憶體模型中的位置。
在這裡插入圖片描述

3. static關鍵字的用途

static可以用來修飾類的成員方法、類的成員變數,另外可以編寫static程式碼塊來優化程式效能。

3.1 static方法

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

3.2 static變數

static變數也稱作靜態變數,靜態變數和非靜態變數的區別是:靜態變數被所有的物件所共享,在記憶體中只有一個副本,它當且僅當在類初次載入時會被初始化。而非靜態變數是物件所擁有的,在建立物件的時候被初始化,存在多個副本,各個物件擁有的副本互不影響。

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

3.3 static程式碼塊

static關鍵字還有一個比較關鍵的作用就是 用來形成靜態程式碼塊以優化程式效能。static塊可以置於類中的任何地方,類中可以有多個static塊。在類初次被載入的時候,會按照static塊的順序來執行每個static塊,並且只會執行一次

4.常見的面試筆試題目

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(),而Person類還沒有被載入過,因此會先載入Person類並執行Person類中的static塊,接著執行父類的構造器,完成了父類的初始化,然後就來初始化自身了,因此會接著執行MyClass中的Person person = new Person(),最後執行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

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

4.總結

  1. 載入的順序:父類的static成員變數 -> 子類的static成員變數 -> 父類的成員變數 -> 父類構造 -> 子類成員變數 -> 子類構造

  2. static只會載入一次,所以通俗點講第一次new的時候,所有的static都先會被全部載入(以後再有new都會忽略),進行預設初始化。在從上往下進行顯示初始化。這裡靜態程式碼塊和靜態成員變數沒有先後之分,誰在上,誰就先初始化

  3. 構造程式碼塊是什麼?把所有構造方法中相同的內容抽取出來,定義到構造程式碼塊中,將來在呼叫構造方法的時候,會去自動呼叫構造程式碼塊。構造程式碼快優先於構造方法。

如果類還沒有被載入:

  1. 先執行父類的靜態程式碼塊和靜態變數初始化,並且靜態程式碼塊和靜態變數的執行順序只跟程式碼中出現的順序有關。
  2. 執行子類的靜態程式碼塊和靜態變數初始化。
  3. 執行父類的例項變數初始化
  4. 執行父類的構造塊
  5. 執行父類的建構函式
  6. 執行子類的例項變數初始化
  7. 執行子類的構造塊
  8. 執行子類的建構函式

如果類已經被載入: 則靜態程式碼塊和靜態變數就不用重複執行,再建立類物件時,只執行與例項相關的變數初始化和構造方法。

下面列出一個最終版執行順序:

public class staticTest {

    public static void main(String[] args) {
        new MyClass();
    }
}

class SuperClass{
    //父類靜態構造程式碼塊
    static{
        System.out.println("superClass static block");
    }

    //父類靜態變數
    private static SuperStaticVariable staticVariable=new SuperStaticVariable();

    //父類實列變數
    private SuperVariable variable=new SuperVariable();

    //父類構造程式碼塊
    {
        System.out.println("superClass block");
    }

    //父類建構函式
    public SuperClass() {
        System.out.println("SuperClass constructor");
    }
}

class SuperStaticVariable{
    public SuperStaticVariable() {
        System.out.println("SuperStaticVariable init");
    }
}

class SuperVariable{
    public SuperVariable() {
        System.out.println("SuperVariable init");
    }
}

class MyClass extends SuperClass {
    //子類靜態變數
    static{
        System.out.println("myclass static");
    }

    //子類靜態變數
    private static MyClassStaticVariable staticVariable = new MyClassStaticVariable();

    //子類實列變數
    private MyClassVariable variable = new MyClassVariable();

    //子類構造程式碼塊
    {
        System.out.println("myclass block");
    }

    //子類建構函式
    public MyClass() {
        System.out.println("myclass constructor");
    }
}

class MyClassStaticVariable{
    public MyClassStaticVariable() {
        System.out.println("MyClassStaticVariable init");
    }
}

class MyClassVariable{
    public MyClassVariable() {
        System.out.println("MyClassVariable init");
    }
}

執行結果:

superClass static block
SuperStaticVariable init
myclass static
MyClassStaticVariable init
SuperVariable init
superClass block
SuperClass constructor
MyClassVariable init
myclass block
myclass constructor