前序文章:深入理解Java類加載
<clinit>()
與 <init>()
區別
一、<clinit>()
Java 類加載的初始化過程中,編譯器按語句在源文件中出現的順序,依次自動收集類中的所有類變量的賦值動作和靜態代碼塊中的語句合並產生 <clinit>() 方法。 如果類中沒有靜態語句和靜態代碼塊,那可以不生成
並且 <clinit>()
不需要顯式調用父類(接口除外,接口不需要調用父接口的初始化方法,只有使用到父接口中的靜態變量時才需要調用)的初始化方法 <clinit>()
,虛擬機會保證在子類的 <clinit>()
方法執行之前,父類的 <clinit>()
方法已經執行完畢。
二、<init>()
對象構造時用以初始化對象的,構造器以及非靜態初始化塊中的代碼。
<clinit>()
與 <init>()
執行順序
直接看代碼
public class Test {
private static Test instance;
static {
System.out.println("static開始");
// 下面這句編譯器報錯,非法向前引用
// System.out.println("x=" + x);
instance = new Test();
System.out.println("static結束");
}
public Test() {
System.out.println("構造器開始");
System.out.println("x=" + x + ";y=" + y);
// 構造器可以訪問聲明於他們后面的靜態變量
// 因為靜態變量在類加載的准備階段就已經分配內存並初始化0值了
// 此時 x=0,y=0
x++;
y++;
System.out.println("x=" + x + ";y=" + y);
System.out.println("構造器結束");
}
public static int x = 6;
public static int y;
public static Test getInstance() {
return instance;
}
public static void main(String[] args) {
Test obj = Test.getInstance();
System.out.println("x=" + obj.x);
System.out.println("y=" + obj.y);
}
}
輸出信息如下:
static開始
構造器開始
x=0;y=0
x=1;y=1
構造器結束
static結束
x=6
y=1
虛擬機首先執行的是類加載初始化過程中的 <clinit>()
方法,也就是靜態變量賦值以及靜態代碼塊中的代碼,如果 <clinit>()
方法中觸發了對象的初始化,也就是 <init>()
方法,那么會進入執行 <init>()
方法,執行 <init>()
方法完成之后,再回來繼續執行 <clinit>()
方法。
上面代碼中,先執行 static 代碼塊,此時調用了構造器,構造器中對類變量 x 和 y 進行加 1 ,之后繼續完 static 代碼塊,接着執行下面的 public static int x = 6;
來重新給類變量 x 賦值為 6,因此,最后輸出的是 x=6, y=1。
如果希望輸出的是 x=7,y=1,很簡單,將語句 public static int x = 6;
移至 static 代碼塊之前就可以了。