java靜態/動態成員變量、初始化塊,父類/子類構造函數執行順序問題

  1. /*
  2. * 幾大原則
  3. * 1、靜態成員變量(Static)
  4. * 一、靜態成員變量爲類變量,全部對象共享同一內存空間
  5. * 二、靜態成員變量的聲明和定義僅在首次加載類時執行一次
  6. * 三、首次加載類時首先對全部靜態成員變量根據類型默認賦初值,而後再對有右值的附右值
  7. * 2、靜態初始塊
  8. * 一、靜態初始化塊僅在首次加載類時執行一次
  9. * ······多個靜態成員變量與靜態始化快參照出現順序前後執行······
  10. * 3、動態成員變量
  11. * 一、動態成員變量定義在每次實例化對象時在構造函數以前執行
  12. * 4、動態初始化塊
  13. * 一、動態初始化塊在每次實例化對象時在構造函數以前執行
  14. * ······多個動態成員變量與動態初始化塊參照出現順序前後執行······
  15. * 總結:總的來講,在不涉及繼承的前提下,當首次加載類時,按照以下順序執行
  16. * 一、按照出現順序前後執行靜態成員變量定義與靜態初始化塊
  17. * 二、按照出現順序前後執行動態成員變量定義與動態初始化塊
  18. * 三、執行構造函數
  19. * 再次實例化對象時只執行第二、3步便可
  20. *
  21. * ············成員變量與定義與初始化塊先於構造函數執行·········
  22. * 5、當涉及到繼承時,按照以下順序執行
  23. * 一、執行父類的靜態成員變量定義與靜態初始化塊,執行子類的靜態成員變量定義與靜態初始化塊
  24. * 二、執行父類的非靜態成員變量定義與動態初始化塊,執行父類構造方法
  25. * 三、執行子類的非靜態成員變量定義與動態初始化塊,執行子類構造方法
  26. * 另:父類構造方法中用到的方法若是已被子類重寫,那麼在構造子類對象時在調用父類構造函數中使用子類重寫的方法
  27. */


1.實例變量和類變量的內存分配

類變量 :使用static修飾的成員變量是類變量,屬於該類自己 java

實例變量:沒有使用static修飾的成員變量是實例變量,屬於該類的實例 ide

因爲同一個JVM內每一個類只對應一個Class對象,所以同一個JVM內的一個類的類變量只需一塊內存空間函數

對於實例變量而言,該類每建立一次實例,就須要爲實例變量分配一塊內存空間,因此,程序中有幾個實例,實例變量就須要幾塊內存空間this

2.類變量的初始化時機老是處於實例變量的初始化以前

咱們先看下下面三段代碼: spa

1)由於兩個實例變量都是在建立變量的時候纔開始分配空間,此時num2尚未分配,因此前向引用就會出現編譯錯誤。 code

1 int num = num2 + 3; //非法前向引用,會報錯 2 int num2 = 2

2)由於兩個類變量在JVM加載類的時候分配空間,此時num2尚未分配,因此前向引用就出現編譯錯誤。 視頻

1 static int num = num2 + 3; //非法前向引用,會報錯 2 static int num2 = 2

3)由於類變量num2在JVM加載類的時候空間已經分配好,而num在建立實例的時候才分配空間,此時num2已經分配成功了,因此num前向引用成功。 對象

1 int num = num2 + 3; //正確使用 2 static int num2 = 2;

由上面三段代碼塊就能夠驗證得:類變量的初始化時機老是處於實例變量的初始化以前 繼承

3.Java對象的初始化方式及其執行順序

Java對象的初始化方式有三種:1)構造器 2)初始化塊 3)定義變量時指定初始化值 內存

若是這三種初始化方式同時出現,也要注意,他們也有一個執行順序的規定:

1)靜態初始化塊只在類第一次建立對象的時候運行一次,後面就不會再運行,而類在每次建立對象時,非靜態初始化塊老是會運行一次。

public class Test{
    static {
        System.out.println("執行---靜態初始化代碼塊.");
    }
    
    {
        System.out.println("執行---非靜態初始化代碼塊.");
    }
    
    public static void main(String[] args) {
        for (int i = 1; i <= 2; i++) {
            System.out.println("建立第 " + i + " 個對象"); 
            new Test();
            System.out.println();
        }
    }
}




運行結果:

2)構造器每次建立對象時,構造器必然有執行的機會,此時,非靜態初始化塊一定也將得到機會而且運行在構造器以前

public class Test{

    {
        System.out.println("執行---非靜態初始化代碼塊.");
    }
    
    public Test() {
        System.out.println("執行---構造器.");
    }
    
    public static void main(String[] args) {
        for (int i = 1; i <= 2; i++) {
            System.out.println("建立第 " + i + " 個對象"); 
            new Test();
            System.out.println();
        }
    }
}




運行結果:

3)定義變量時指定的初始化值和初始化塊中指定的初始值的執行順序與他們在源程序中的排列順序相同。

驗證代碼一:

public class Test{
    
    String i = "定義變量時指定的初始化值";

    {
        i = "初始化塊中指定的初始值";
    }
    
    public static void main(String[] args) {
        for (int i = 1; i <= 2; i++) {
            System.out.println("建立第 " + i + " 個對象"); 
            System.out.println(new Test().i);
            System.out.println();
        }
    }
}




運行結果

驗證代碼二 :

public class Test{

    {
        i = "初始化塊中指定的初始值";
    }
    
    String i = "定義變量時指定的初始化值";

    public static void main(String[] args) {
        for (int i = 1; i <= 2; i++) {
            System.out.println("建立第 " + i + " 個對象"); 
            System.out.println(new Test().i);
            System.out.println();
        }
    }
}




運行結果:

4.關於父子實例的內存控制

(通常狀況下是不用內部類來驗證的,可是都是同樣的啦,我偷懶下,因此使用了內部類,你們原諒哈)

1)當子類重寫父類方法後,父類表面上只是調用屬於本身的被子類重寫的方法。

public class Test{
    class Base {
        Base() {
            this.info();
        }
        public void info() {
            System.out.println("Base");
        }
        public void getInfo() {
            info();
        }
    }
    
    public class Child extends Base{
        @Override
        public void info() {
            System.out.println("Child");
        }
    }
    
    public static void main(String[] args) {
        Test test = new Test();
        Base base = test.new Child();
        base.info();
        base.getInfo();
    }
}




運行結果:

2)上述是屬於多態中方法的體現,可是方法有多態,實例變量無多態。

解釋下「方法有多態,變量無多態」這句話:意思是,無論怎樣,父類表面上只是調用屬於本身的被子類重寫的方法。而變量不同,假設父類和子類都有同一個變量名的實例變量,向上轉型後,經過父類訪問的實例變量獲得的值是自身的而非子類的。向下轉型後,經過子類訪問的實例變量獲得的值是自身的而非父類的。

不少書上或教學視頻上都講,建立一個子類對象的時候,Java 會順着繼承結構往上一直找到 Object,而後從 Object 開始往下依次執行構造函數。先執行父類的構造函數,而後在其子類中會建立一個成員變量指向他的父類。其實這個說法是錯誤的系統並不會真正的去建立父類對象,只是在子類對象中不只保存了自己的實例變量,還有它父類的所有實例變量。

public class Test{
    class Base {    //父類
        int i = 2;
    }
    
    public class Child extends Base{    //子類
        int i  = 20;
    }
    
    public static void main(String[] args) {
        Test test = new Test();
        Child child = test.new Child();
        Base base = child;
        System.out.println(" Base.i : " + base.i);
        System.out.println("Child.i : " + child.i);
    }
}




運行結果:

5.final修飾符

final變量在編譯時就被肯定下來了,至關於一個直接量。

1)final修飾的實例變量賦值時機:

  • 定義final實例變量時 指定初始值 
  • 非靜態初始化模塊中爲final實例變量指定的初始值
  • 在構造器中爲final實例變量指定初始值   

2)final修飾的類變量賦值時機: 

  • 定義final類變量時指定初始值
  • 靜態初始化模塊中爲final實例變量指定的初始值
相關文章
相關標籤/搜索