1. 程式人生 > 實用技巧 >Java中的static總結

Java中的static總結

  • static修飾變數。在類載入的時候,初始化順序按照類定義的順序執行。也就是父類順序初始化—>子類順序初始化。只執行一次
  • static修飾程式碼塊。在類載入的時候,按照類定義的順序執行。也就是父類順序執行static塊—>子類順序執行static塊。只執行一次

static方法就是沒有this的方法。在static方法內部不能呼叫非靜態方法,反過來是可以的。而且可以在沒有建立任何物件的前提下,僅僅通過類本身來呼叫static方法。這實際上正是static方法的主要用途。 ——Java程式設計思想

讀完上面的引言後,這裡需要強調一點,在非靜態方法中,是可以通過this來呼叫static變數、方法的。

static常見修飾

1 static修飾方法

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

  雖然在靜態方法中不能訪問非靜態成員方法和非靜態成員變數,但是在非靜態成員方法中是可以訪問靜態成員方法/變數的。

class MyObject{
    private static String str1 = "staticProperty";
    private String str2 = "property";
    
    public MyObject(){
        
    }
    
    public void print1(){
        System.out.println(str1);  // 可以訪問靜態變數
        System.out.println(str2);
        print2();				   // 可以訪問靜態方法
    }
    
    public static void print2(){
        System.out.println(str1);
        System.out.println(str2);  // 編譯出錯,不能訪問非靜態變數
        print1();				   // 編譯出錯,不能訪問非靜態方法
    }
}

  在上面的程式碼中,由於print2方法是獨立於物件存在的,可以直接用過類名呼叫。

  因此,如果說想在不建立物件的情況下呼叫某個方法,就可以將這個方法設定為static。我們最常見的static方法就是main方法,至於為什麼main方法必須是static的,現在就很清楚了。因為程式在執行main方法的時候沒有建立任何物件,因此只有通過類名來訪問。

2 static修飾成員變數

  static變數也稱作靜態變數,靜態變數和非靜態變數的區別是:

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

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

3 static修飾程式碼塊

  比如JDBC中通過靜態塊載入資源

static{
    /**
	 * 6.解析資原始檔
	 */
	//6.1 獲取資原始檔解析器物件
	ResourceBundle bundle=ResourceBundle.getBundle("DBOptions");
	//6.2從資源獲取資料,填充四項變數
	driver=bundle.getString("DRIVER");
	url=bundle.getString("URL");
	userName=bundle.getString("USERNAME");
	password=bundle.getString("PASSWORD");
	Class.forName(driver);
}

  static塊可以置於類中的任何地方,類中可以有多個static塊。在類初次被載入的時候,會按照static塊的順序來執行每個static塊,並且只會執行一次。

4 static修飾類

  static修飾類只有一種情況,那就是這個類屬於靜態內部類,接觸過Android開發的話可能遇見過很多這樣的靜態內部類,如WindowManager.LayoutParams類,LayoutParams就是WindowManager類下的靜態內部類,它的原始碼如下所示:

public interface WindowManager extends ViewManager {
    public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
        //內部實現
    }
}

static誤區

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

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

public class Main{
    public static void main(String[] args){
        System.out.println(Person.name);
        System.out.println(Person.age);  // 編譯出錯,欄位Person.age不可見
    }
}

class Person{
    public static String name = "小曾";
    private static int age = 18;
}

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

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

  這裡面主要考察對thisstatic的理解。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 下面這段程式碼的輸出結果是什麼?

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

重要總結

  經過以上的講解與案例後,我們總結出一個類啟動後的執行過程

  • 載入類:通過JVM呼叫ClassLoader類中的loaderClass()方法載入我們要執行的類
  • 執行static修飾的程式碼塊(從上往下執行,並且只執行一次)
  • 呼叫構造器
  • 成員變數初始化(注意不是在建構函式中初始化)
  • 執行構造方法(這時才是建構函式初始化)
  • 物件建立完畢