1 init和clinit區(qū)別
1.1 init和clinit方法執(zhí)行時(shí)機(jī)不同
init是對(duì)象構(gòu)造器方法,也就是說在程序執(zhí)行 new 一個(gè)對(duì)象調(diào)用該對(duì)象類的 constructor 方法時(shí)才會(huì)執(zhí)行init方法
類初始化時(shí)機(jī)
clinit是類構(gòu)造器方法,也就是在jvm進(jìn)行類加載—–驗(yàn)證—-解析—–初始化,中的初始化階段jvm會(huì)調(diào)用clinit方法。
1.2 init和clinit方法執(zhí)行目的不同
- init是instance實(shí)例構(gòu)造器,對(duì)非靜態(tài)變量解析初始化
- clinit是class類構(gòu)造器對(duì)靜態(tài)變量,靜態(tài)代碼塊進(jìn)行初始化。
class X {
static Log log = LogFactory.getLog(); // <clinit>
private int x = 1; // <init>
X(){
// <init>
}
static {
// <clinit>
}
}
2 clinit詳解
在準(zhǔn)備階段,變量已經(jīng)賦過一次系統(tǒng)要求的初始值,初始化階段是執(zhí)行類構(gòu)造器<clinit>()方法的過程。
2.1 <clinit>
<clinit>()方法是由編譯器自動(dòng)收集類中的所有類變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊(static{}塊)中的語(yǔ)句合并產(chǎn)生的
編譯器收集的順序是由語(yǔ)句在源文件中出現(xiàn)的順序所決定的,靜態(tài)語(yǔ)句塊中只能訪問到定義在靜態(tài)語(yǔ)句塊之前的變量,定義在它之后的變量,在前面的靜態(tài)語(yǔ)句塊可以賦值,但是不能訪問如下代碼
public class Test{
static{
i=0;//給變量賦值可以正常編譯通過
System.out.print(i);//這句編譯器會(huì)提示"非法向前引用"
}
static int i=1;
}
2.2 父類的<clinit>
虛擬機(jī)會(huì)保證在子類的<clinit>()方法執(zhí)行之前,父類的<clinit>()方法已經(jīng)執(zhí)行完畢。
public class Test{
static{
i=0;//給變量賦值可以正常編譯通過
System.out.print(i);//這句編譯器會(huì)提示"非法向前引用"
}
static int i=1;
}
因此在虛擬機(jī)中第一個(gè)被執(zhí)行的<clinit>()方法的類肯定是java.lang.Object。
由于父類的<clinit>()方法先執(zhí)行,也就意味著父類中定義的靜態(tài)語(yǔ)句塊要優(yōu)先于子類的變量賦值操作,
如下代碼中,字段B的值將會(huì)是2而不是1。
static class Parent{
public static int A=1;
static{
A=2;}
static class Sub extends Parent{
public static int B=A;
}
public static void main(String[]args){
System.out.println(Sub.B);
}
}
2.3 接口
接口中不能使用靜態(tài)語(yǔ)句塊,但仍然有變量初始化的賦值操作,因此接口與類一樣都會(huì)生成<clinit>()方法。
但接口與類不同的是,執(zhí)行接口的<clinit>()方法不需要先執(zhí)行父接口的<clinit>()方法。
只有當(dāng)父接口中定義的變量使用時(shí),父接口才會(huì)初始化。 另外,接口的實(shí)現(xiàn)類在初始化時(shí)也一樣不會(huì)執(zhí)行接口的<clinit>()方法。
接口中的屬性都是static final類型的常量,因此在準(zhǔn)備階段就已經(jīng)初始化話