天天看點

Java父類與子類中靜态代碼塊 執行個體代碼塊 靜态變量 執行個體變量 構造函數執行順序

執行個體化子類時,父類與子類中的靜态代碼塊、執行個體代碼塊、靜态變量、執行個體變量、構造函數的執行順序是怎樣的?

代碼執行的優先級為:

firest:靜态部分

second:執行個體化過程

詳細順序為:

1.父類靜态代碼塊與父類靜态變量指派(取決于代碼書寫順序)

2.子類靜态代碼塊與子類靜态變量指派(取決于代碼書寫順序)

3.父類執行個體變量指派與父類代碼塊(取決于代碼書寫順序)

4.父類構造函數

5.子類執行個體變量指派與子類代碼塊(取決于代碼書寫順序)

6.子類構造函數

在JVM加載完類以後,類在被使用的時候初始化,靜态部分隻在類初始化的時候執行一次。

測試代碼:

class Father {

    Father() {
        LogUtil.log(System.currentTimeMillis() + " ------ 父類構造函數");
    }

    static {
        LogUtil.log(System.currentTimeMillis() + " ------ 父類靜态代碼塊");
    }

    long x = getTime(" ------ 父類執行個體變量指派");

    {
        long time = System.currentTimeMillis();
        LogUtil.log(time + " ------ 父類代碼塊");
    }

    static long y = getTime(" ------ 父類靜态變量指派");

    static long getTime(String who) {
        long time = System.currentTimeMillis();
        LogUtil.log(time + who);
        return time;
    }
}

class Child extends Father {

    Child() {
        LogUtil.log(System.currentTimeMillis() + " ------ 子類構造函數");
    }

    static long y = getTime(" ------ 子類靜态變量指派");

    static {
        LogUtil.log(System.currentTimeMillis() + " ------ 子類靜态代碼塊");
    }

    {
        long time = System.currentTimeMillis();
        LogUtil.log(time + " ------ 子類代碼塊");
    }

    long x = getTime(" ------ 子類執行個體變量指派");

    static long getTime(String who) {
        long time = System.currentTimeMillis();
        LogUtil.log(time + who);
        return time;
    }
}
           

調用代碼:

new Thread(new Runnable() {
            @Override
            public void run() {
                new Child();
                LogUtil.log("分隔符 ------ 分隔符");
                new Child();
            }
        }).start();
           

執行結果:

11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523353 ------ 父類靜态代碼塊
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523353 ------ 父類靜态變量指派
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523353 ------ 子類靜态變量指派
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523353 ------ 子類靜态代碼塊
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523353 ------ 父類執行個體變量指派
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523353 ------ 父類代碼塊
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523353 ------ 父類構造函數
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523353 ------ 子類代碼塊
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523353 ------ 子類執行個體變量指派
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523353 ------ 子類構造函數
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 分隔符 ------ 分隔符
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523353 ------ 父類執行個體變量指派
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523353 ------ 父類代碼塊
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523353 ------ 父類構造函數
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523354 ------ 子類代碼塊
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523354 ------ 子類執行個體變量指派
11-03 20:02:03.350 7533-7881/? E/AKADDEMO: 1478174523354 ------ 子類構造函數
           

-------------2018.1.5---------------

我們通過反編譯工具(IntelliJ IDEA或JD-JUI)反編譯一下class檔案看能發現什麼有意思的東西

測試代碼:

class Father {

    Father() {
        System.out.print(System.currentTimeMillis() + " ------ 父類構造函數" + '\n');
    }

    private static long a = getTime(" -----a 父類靜态變量指派" + '\n');
    private final static long b = getTime(" -----b 父類靜态變量指派" + '\n');

    static {
        System.out.print(System.currentTimeMillis() + " ------ 父類靜态代碼塊" + '\n');
    }

    long aa = getTime(" ----aa 父類執行個體變量指派" + '\n');

    {
        System.out.print(System.currentTimeMillis() + " ------ 父類代碼塊" + '\n');
    }

    long bb = getTime(" ----bb 父類執行個體變量指派" + '\n');

    static long z = getTime(" -----z 父類靜态變量指派" + '\n');

    static long getTime(String who) {
        long time = System.currentTimeMillis();
        System.out.print(time + who);
        return time;
    }

    private static long c = getTime(" -----c 父類靜态變量指派" + '\n');
    private final static long d = getTime(" -----d 父類靜态變量指派" + '\n');
}
           

調用代碼:

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

執行結果:

1515134833612 -----a 父類靜态變量指派
1515134833614 -----b 父類靜态變量指派
1515134833614 ------ 父類靜态代碼塊
1515134833614 -----z 父類靜态變量指派
1515134833614 -----c 父類靜态變量指派
1515134833614 -----d 父類靜态變量指派
1515134833614 ----aa 父類執行個體變量指派
1515134833614 ------ 父類代碼塊
1515134833614 ----bb 父類執行個體變量指派
1515134833614 ------ 父類構造函數
           

用 IntelliJ IDEA 打開上述代碼編譯之後的 Father.class :

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.ak.torch.videoplayer.disassamble;

public class Father {
    private static long a = getTime(" -----a 父類靜态變量指派\n");
    private static final long b = getTime(" -----b 父類靜态變量指派\n");
    long aa = getTime(" ----aa 父類執行個體變量指派\n");
    long bb;
    static long z;
    private static long c;
    private static final long d;

    Father() {
        System.out.print(System.currentTimeMillis() + " ------ 父類代碼塊" + '\n');
        this.bb = getTime(" ----bb 父類執行個體變量指派\n");
        System.out.print(System.currentTimeMillis() + " ------ 父類構造函數" + '\n');
    }

    static long getTime(String var0) {
        long var1 = System.currentTimeMillis();
        System.out.print(var1 + var0);
        return var1;
    }

    static {
        System.out.print(System.currentTimeMillis() + " ------ 父類靜态代碼塊" + '\n');
        z = getTime(" -----z 父類靜态變量指派\n");
        c = getTime(" -----c 父類靜态變量指派\n");
        d = getTime(" -----d 父類靜态變量指派\n");
    }
}
           

可以發現:

1、在 Father.java 中靜态變量a、b的指派操作寫在了靜态代碼塊的前面,靜态變量z、c、d的指派操作寫在了靜态代碼塊的後面,檢視反編譯之後的代碼,可見靜态變量z、c、d的指派被寫在了靜态代碼塊中。

2、在 Father.java 中執行個體變量aa的指派操作寫在了執行個體代碼塊的前面,執行個體變量bb的指派操作寫在了執行個體代碼塊的後面,檢視反編譯之後的代碼,可見代碼塊中的操作和執行個體變量b的指派都被寫在了構造函數中。

下面分情況删除一些代碼,我們看一下反編譯之後的不同

情況一:删掉靜态代碼塊,此時的 Father.java 代碼:

class Father {

    Father() {
        System.out.print(System.currentTimeMillis() + " ------ 父類構造函數" + '\n');
    }

    private static long a = getTime(" -----a 父類靜态變量指派" + '\n');
    private final static long b = getTime(" -----b 父類靜态變量指派" + '\n');

    long aa = getTime(" ----aa 父類執行個體變量指派" + '\n');

    {
        System.out.print(System.currentTimeMillis() + " ------ 父類代碼塊" + '\n');
    }

    long bb = getTime(" ----bb 父類執行個體變量指派" + '\n');

    static long z = getTime(" -----z 父類靜态變量指派" + '\n');

    static long getTime(String who) {
        long time = System.currentTimeMillis();
        System.out.print(time + who);
        return time;
    }

    private static long c = getTime(" -----c 父類靜态變量指派" + '\n');
    private final static long d = getTime(" -----d 父類靜态變量指派" + '\n');
}
           

用 IntelliJ IDEA 打開上述代碼編譯之後的 Father.class :

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.ak.torch.videoplayer.disassamble;

public class Father {
    private static long a = getTime(" -----a 父類靜态變量指派\n");
    private static final long b = getTime(" -----b 父類靜态變量指派\n");
    long aa = getTime(" ----aa 父類執行個體變量指派\n");
    long bb;
    static long z = getTime(" -----z 父類靜态變量指派\n");
    private static long c = getTime(" -----c 父類靜态變量指派\n");
    private static final long d = getTime(" -----d 父類靜态變量指派\n");

    Father() {
        System.out.print(System.currentTimeMillis() + " ------ 父類代碼塊" + '\n');
        this.bb = getTime(" ----bb 父類執行個體變量指派\n");
        System.out.print(System.currentTimeMillis() + " ------ 父類構造函數" + '\n');
    }

    static long getTime(String var0) {
        long var1 = System.currentTimeMillis();
        System.out.print(var1 + var0);
        return var1;
    }
}
           

可以發現:

任何一個靜态變量的指派都沒有被寫在靜态代碼塊中

情況二:删掉執行個體代碼塊,此時的 Father.java 代碼:

class Father {

    Father() {
        System.out.print(System.currentTimeMillis() + " ------ 父類構造函數" + '\n');
    }

    private static long a = getTime(" -----a 父類靜态變量指派" + '\n');
    private final static long b = getTime(" -----b 父類靜态變量指派" + '\n');

    static {
        System.out.print(System.currentTimeMillis() + " ------ 父類靜态代碼塊" + '\n');
    }

    long aa = getTime(" ----aa 父類執行個體變量指派" + '\n');

    long bb = getTime(" ----bb 父類執行個體變量指派" + '\n');

    static long z = getTime(" -----z 父類靜态變量指派" + '\n');

    static long getTime(String who) {
        long time = System.currentTimeMillis();
        System.out.print(time + who);
        return time;
    }

    private static long c = getTime(" -----c 父類靜态變量指派" + '\n');
    private final static long d = getTime(" -----d 父類靜态變量指派" + '\n');
}
           

用 IntelliJ IDEA 打開上述代碼編譯之後的 Father.class :

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.ak.torch.videoplayer.disassamble;

public class Father {
    private static long a = getTime(" -----a 父類靜态變量指派\n");
    private static final long b = getTime(" -----b 父類靜态變量指派\n");
    long aa = getTime(" ----aa 父類執行個體變量指派\n");
    long bb = getTime(" ----bb 父類執行個體變量指派\n");
    static long z;
    private static long c;
    private static final long d;

    Father() {
        System.out.print(System.currentTimeMillis() + " ------ 父類構造函數" + '\n');
    }

    static long getTime(String var0) {
        long var1 = System.currentTimeMillis();
        System.out.print(var1 + var0);
        return var1;
    }

    static {
        System.out.print(System.currentTimeMillis() + " ------ 父類靜态代碼塊" + '\n');
        z = getTime(" -----z 父類靜态變量指派\n");
        c = getTime(" -----c 父類靜态變量指派\n");
        d = getTime(" -----d 父類靜态變量指派\n");
    }
}
           

可以發現:

任何一個執行個體變量的指派都沒有被寫在構造函數中

情況三:删掉靜态變量,此時的 Father.java 代碼:

class Father {

    Father() {
        System.out.print(System.currentTimeMillis() + " ------ 父類構造函數" + '\n');
    }

    static {
        System.out.print(System.currentTimeMillis() + " ------ 父類靜态代碼塊" + '\n');
    }

    long aa = getTime(" ----aa 父類執行個體變量指派" + '\n');

    {
        System.out.print(System.currentTimeMillis() + " ------ 父類代碼塊" + '\n');
    }

    long bb = getTime(" ----bb 父類執行個體變量指派" + '\n');

    static long getTime(String who) {
        long time = System.currentTimeMillis();
        System.out.print(time + who);
        return time;
    }
}
           

用 IntelliJ IDEA 打開上述代碼編譯之後的 Father.class :

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.ak.torch.videoplayer.disassamble;

public class Father {
    long aa = getTime(" ----aa 父類執行個體變量指派\n");
    long bb;

    Father() {
        System.out.print(System.currentTimeMillis() + " ------ 父類代碼塊" + '\n');
        this.bb = getTime(" ----bb 父類執行個體變量指派\n");
        System.out.print(System.currentTimeMillis() + " ------ 父類構造函數" + '\n');
    }

    static long getTime(String var0) {
        long var1 = System.currentTimeMillis();
        System.out.print(var1 + var0);
        return var1;
    }

    static {
        System.out.print(System.currentTimeMillis() + " ------ 父類靜态代碼塊" + '\n');
    }
}
           

情況四:删掉執行個體變量,此時的 Father.java 代碼:

class Father {

    Father() {
        System.out.print(System.currentTimeMillis() + " ------ 父類構造函數" + '\n');
    }

    private static long a = getTime(" -----a 父類靜态變量指派" + '\n');
    private final static long b = getTime(" -----b 父類靜态變量指派" + '\n');

    static {
        System.out.print(System.currentTimeMillis() + " ------ 父類靜态代碼塊" + '\n');
    }

    {
        System.out.print(System.currentTimeMillis() + " ------ 父類代碼塊" + '\n');
    }

    static long z = getTime(" -----z 父類靜态變量指派" + '\n');

    static long getTime(String who) {
        long time = System.currentTimeMillis();
        System.out.print(time + who);
        return time;
    }

    private static long c = getTime(" -----c 父類靜态變量指派" + '\n');
    private final static long d = getTime(" -----d 父類靜态變量指派" + '\n');
}
           

用 IntelliJ IDEA 打開上述代碼編譯之後的 Father.class :

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.ak.torch.videoplayer.disassamble;

public class Father {
    private static long a = getTime(" -----a 父類靜态變量指派\n");
    private static final long b = getTime(" -----b 父類靜态變量指派\n");
    static long z;
    private static long c;
    private static final long d;

    Father() {
        System.out.print(System.currentTimeMillis() + " ------ 父類代碼塊" + '\n');
        System.out.print(System.currentTimeMillis() + " ------ 父類構造函數" + '\n');
    }

    static long getTime(String var0) {
        long var1 = System.currentTimeMillis();
        System.out.print(var1 + var0);
        return var1;
    }

    static {
        System.out.print(System.currentTimeMillis() + " ------ 父類靜态代碼塊" + '\n');
        z = getTime(" -----z 父類靜态變量指派\n");
        c = getTime(" -----c 父類靜态變量指派\n");
        d = getTime(" -----d 父類靜态變量指派\n");
    }
}
           

總結一下:

一、靜态代碼塊 與 靜态變量指派 在反編譯成的代碼中的位置

1、如果存在靜态代碼塊,并且有靜态變量的指派寫在了靜态代碼塊的下面,那麼這樣的靜态變量最終的指派操作會被寫在靜态代碼塊中

2、如果存在多個靜态代碼塊,最終會合并為一個

二、執行個體變量指派 與 執行個體代碼塊 在反編譯成的代碼中的位置

1、如果存在執行個體代碼塊,并且有執行個體變量的指派寫在了執行個體代碼塊的下面,那麼執行個體代碼塊中的操作和這樣的執行個體變量最終的指派操作會被寫在構造函數中

2、執行個體代碼塊中的操作都會被寫在構造函數中

3、如果存在多個執行個體代碼塊,都會被寫在構造函數中

了解了變量指派與代碼塊的執行順序,就能從代碼執行的角度解釋有時候在代碼塊中使用變量時為什麼會提示“非法前向引用(illegal forward reference)”

反編譯工具 IntelliJ IDEA 的BUG

根據 靜态代碼塊 與 靜态變量指派 的執行順序取決于代碼書寫順序這一原則,測試Father.java

class Father {

    static {
        bb = getTime(" ----bb 父類執行個體變量" + '\n');
    }

    static long aa = getTime(" ----aa 父類執行個體變量" + '\n');
    static long bb;

    static long getTime(String who) {
        long time = System.currentTimeMillis();
        System.out.print(time + who);
        return time;
    }
}
           

執行結果:

1515149709262 ----bb 父類執行個體變量
1515149709262 ----aa 父類執行個體變量
           

用 IntelliJ IDEA 打開上述代碼編譯之後的 Father.class

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.ak.torch.videoplayer.disassamble;

public class Father {
    static long aa = getTime(" ----aa 父類執行個體變量\n");
    static long bb = getTime(" ----bb 父類執行個體變量\n");

    public Father() {
    }

    static long getTime(String var0) {
        long var1 = System.currentTimeMillis();
        System.out.print(var1 + var0);
        return var1;
    }
}
           

将此反編譯之後的代碼重新搞一個java檔案,然後編譯運作:

1515151279918 ----aa 父類執行個體變量
1515151279919 ----bb 父類執行個體變量
           

可見由IntelliJ IDEA反編譯出的代碼的執行順序已經不是原來類該有的順序了,IntelliJ IDEA反編譯處理的 執行個體代碼塊 與 執行個體變量的執行順序也有類似問題。

經過測試,用 JD-JUI 反編譯也會有上述的問題,但是相容性比 IntelliJ IDEA 好一些。

是以,這兩款反編譯工具反編譯出來的代碼并不能保證跟原來類的邏輯完全一緻。

使用javap指令可以擷取類初始化時代碼的正确執行順序。