Java虛擬機JVM學習04 類的初始化

 

Java虛擬機JVM學習04 類的初始化

類的初始化

  在初始化階段,Java虛擬機執行類的初始化語句,爲類的靜態變量賦予初始值。java

  在程序中,靜態變量的初始化有兩種途徑:dom

  1.在靜態變量的聲明處進行初始化;學習

  2.在靜態代碼塊中進行初始化。spa

  沒有通過顯式初始化的靜態變量將原有的值。code

 

  一個比較奇怪的例子:視頻

package com.mengdd.classloader;

class Singleton {

    // private static Singleton mInstance = new Singleton();// 位置1
    // 位置1輸出:
    // counter1: 1
    // counter2: 0
    public static int counter1;
    public static int counter2 = 0;

    private static Singleton mInstance = new Singleton();// 位置2

    // 位置2輸出:
    // counter1: 1
    // counter2: 1

    private Singleton() {
        counter1++;
        counter2++;
    }

    public static Singleton getInstantce() {
        return mInstance;
    }
}

public class Test1 {

    public static void main(String[] args) {

        Singleton singleton = Singleton.getInstantce();
        System.out.println("counter1: " + Singleton.counter1);
        System.out.println("counter2: " + Singleton.counter2);
    }
}

 

  可見將生成對象的語句放在兩個位置,輸出是不同的(相應位置的輸出已在程序註釋中標明)。對象

  這是由於初始化語句是按照順序來執行的。blog

  靜態變量的聲明語句,以及靜態代碼塊都被看作類的初始化語句,Java虛擬機會按照初始化語句在類文件中的前後順序來依次執行它們。教程

 

類的初始化步驟

  1.假如這個類尚未被加載和鏈接,那就先進行加載和鏈接接口

  2.假如類存在直接的父類,而且這個父類尚未被初始化,那就先初始化直接的父類

  3.假如類中存在初始化語句,那就依次執行這些初始化語句。

 

類的初始化時機

  Java程序對類的使用方式能夠分爲兩種:

  1.主動使用

  2.被動使用

  全部的Java虛擬機實現必須在每一個類或接口被Java程序首次主動使用才初始化它們。

 

  主動使用的六種狀況:

  1.建立類的實例。

new Test();

  

  2.訪問某個類或接口的靜態變量,或者對該靜態變量賦值。

int b = Test.a;
Test.a = b;

  

  3.調用類的靜態方法

Test.doSomething();

  

  4.反射

Class.forName(「com.mengdd.Test」);

  

  5.初始化一個類的子類

class Parent{
}
class Child extends Parent{
      public static int a = 3;
}
Child.a = 4;

  

  6.Java虛擬機啓動時被標明爲啓動類的類

java com.mengdd.Test

 

   除了以上六種狀況,其餘使用Java類的方式都被看做是對類的被動使用,都不會致使類的初始化

 

接口的特殊性

  當Java虛擬機初始化一個類時,要求它的全部父類都已經被初始化,可是這條規則並不適用於接口

    在初始化一個類時,並不會先初始化它所實現的接口。

    在初始化一個接口時,並不會先初始化它的父接口。

  所以,一個父接口並不會由於它的子接口或者實現類的初始化而初始化,只有當程序首次使用特定接口的靜態變量時,纔會致使該接口的初始化。

 

final類型的靜態變量

  final類型的靜態變量是編譯時常量仍是變量,會影響初始化語句塊的執行。

  若是一個靜態變量的值是一個編譯時的常量就不會對類型進行初始化(類的static塊不執行);

  若是一個靜態變量的值是一個非編譯時的常量,即只有運行時會有肯定的初始化值,則就會對這個類型進行初始化(類的static塊執行)。

 

  例子代碼:

package com.mengdd.classloader;

import java.util.Random;

class FinalTest1 {
    public static final int x = 6 / 3; // 編譯時期已經可知其值爲2,是常量
    // 類型不須要進行初始化
    static {
        System.out.println("static block in FinalTest1");
        // 此段語句不會被執行,即無輸出
    }
}

class FinalTest2 {
    public static final int x = new Random().nextInt(100);// 只有運行時才能獲得值
    static {
        System.out.println("static block in FinalTest2");
        // 會進行類的初始化,即靜態語句塊會執行,有輸出
    }
}

public class InitTest {

    public static void main(String[] args) {
        System.out.println("FinalTest1: " + FinalTest1.x);
        System.out.println("FinalTest2: " + FinalTest2.x);
    }
}

 

 

主動使用的歸屬明確性

  只有當程序訪問的靜態變量或靜態方法確實在當前類或當前接口中定義時,才能夠認爲是對類或接口的主動使用。

 

package com.mengdd.classloader;

class Parent {
    static int a = 3;

    static {
        System.out.println("Parent static block");
    }

    static void doSomething() {
        System.out.println("do something");
    }
}

class Child extends Parent {
    static {
        System.out.println("Child static block");
    }
}

public class ParentTest {

    public static void main(String[] args) {

        System.out.println("Child.a: " + Child.a);
        Child.doSomething();

        // Child類的靜態代碼塊沒有執行,說明Child類沒有初始化
        // 這是由於主動使用的變量和方法都是定義在Parent類中的
    }
}

 

ClassLoader類

  調用ClassLoader類的loadClass()方法加載一個類,並非對類的主動使用,不會致使類的初始化。

 

package com.mengdd.classloader;

class CL {

    static {
        System.out.println("static block in CL");
    }
}

public class ClassLoaderInitTest {

    public static void main(String[] args) throws Exception {
        ClassLoader loader = ClassLoader.getSystemClassLoader();
        Class<?> clazz = loader.loadClass("com.mengdd.classloader.CL");
        // loadClass方法加載一個類,並非對類的主動使用,不會致使類的初始化

        System.out.println("----------------");
        clazz = Class.forName("com.mengdd.classloader.CL");
    }

}

 

參考資料

  聖思園張龍老師Java SE系列視頻教程。

相關文章
相關標籤/搜索