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修飾的代碼塊(從上往下執行,並且只執行一次)
  • 調用構造器
  • 成員變量初始化(注意不是在構造函數中初始化)
  • 執行構造方法(這時纔是構造函數初始化)
  • 對象創建完畢
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章