類的初始化時機
在上篇文章中講到了類的六種主動使用方式,反射是其中的一種(Class.forName(“com.jack.test”)),這裡需要注意一點:當調用ClasLoader類的loadClass方法對類進行加載的時候,并不是對類的主動調用,不會導緻類的初始化。
那麼接下來我繼續給大家2個例子,讓我們來看看他們的執行結果分别是什麼樣的,看看你能猜對嗎?
public class Test2
{
public static void main(String[] args)
{
System.out.println(FinalTest2.x);
}
}
class FinalTest2{
public static final int x=6/2;
static {
System.out.println("I am a final x");
}
}

public class Test2
{
public static void main(String[] args)
{
System.out.println(FinalTest2.x);
}
}
class FinalTest2{
public static final int x=new Random().nextInt();
static {
System.out.println("I am a final x");
}
}
是不是看到結果很意外呢,2份代碼看起來幾乎是一模一樣的,而且都是static修飾的,為什麼和上篇文章講的不一樣了呢,為什麼第一個demo裡面的靜态塊沒有執行呢? 下面讓我們帶着這一系列問題來解決下。
講解
如果大家細心的話可以看到多了一個final修飾符,是的,結果的造成就是它在起作用。
public static final int x=6/2; 這行代碼,java虛拟機在編譯期就可以知道x的值是什麼,是以在編譯期就已經把3放到了常量池,是以在main方法中調用的時候不會觸發類的初始化 即此時的x為編譯期的常量
public static final int x=new Random().nextInt(); 這行代碼中的x在編譯期不能确定具體的值,需要等到運作的時候才能确定x的值,是以在運作時會觸發類的初始化,即此時的x為編譯器的變量
接口和父類
前面講的類主動加載的7種方式,都是再說單個類的情況,下面我們來介紹下接口和父類
當Java虛拟機初始化一個類的時候,要求他的所有父類都已經被初始化,但是如果此類實作的有接口,則:
- 在初始化一個類的時候,并不會先初始化它所實作的接口
- 在初始化一個接口的時候,并不會先初始化它的父接口
是以,一個父接口并不會因為他的子接口或者實作類的初始化而初始化。隻有當程式首次使用他的靜态變量時,才會導緻該接口的初始化。
隻有當程式通路的靜态變量或靜态方法确實在目前類或目前接口中定義時,才可以認為是對類或接口的主動使用。這句話是什麼意思呢?下面讓我們看下這個Demo
public class Test3
{
public static void main(String[] args)
{
System.out.println(Child.x);
}
}
class Parent{
public static int x=3;
static {
System.out.println("this is a parent");
}
}
class Child extends Parent{
static {
System.out.println("this is a Child");
}
}
public class Test3
{
public static void main(String[] args)
{
System.out.println(Child.x);
}
}
class Parent{
public static int x=3;
static {
System.out.println("this is a parent");
}
}
class Child extends Parent{
public static int x=3;
static {
System.out.println("this is a Child");
}
}
上面兩個例子同樣是相差一行代碼,結果卻差别很大,一個Child發生了初始化,另一個沒有。
雙親委派模型
類加載器用來把類加載到Java虛拟機中,從JDK1.2版本開始,類的加載過程采用雙親委派模型機制,這種機制能更好的保證Java平台的安全,在這種機制中,除了java虛拟機自帶的根加載器以外(根加載器由c++實作,沒有父加載器),其餘的類加載器有且隻有一個父加載器。當Java程式請求加載Loader1加載一個類的時候,loader1首先會委托自己的父加載器去加載這個類,若父加載器還有父加載器的話,依次類推,如果父加載器能加在,則有父加載器完成加載,否則才會有loader1本身加載。
需要注意的事,這裡的加載器之間的父子關系實際上指的是加載器對象之間的包裝關系,并不是類之間的繼承關系。一對父子加載器可能是同一個加載器類的2個執行個體,也可能不是,隻是在子加載器對象中包裝了一個父加載器對象。
若有一個類加載器能成功加載Sample類,那麼這個類加載器被成為定義類加載器,所有能成功傳回Clas對象的引用的類加載器(包括定義類加載器)都被稱為初始類加載器。
雙親委派模型的有點是能夠提高軟體系統的安全性。在此機制下,使用者自定義的類加載器不可能加載應該由父加載器加載的可靠類,進而防止不可靠甚至惡意的代碼代替由父加載器加載的可靠代碼。例如:java.lang.Object類總是由根加載器加載,其他任何使用者自定義的類加載器都不可能加載該類。
運作時包
由同一類加載器加載的屬于相同包的類組成了運作時包,決定兩個類是不是屬于同一個運作時包,不僅要看他們的包名是否相同,還要看定義類加載器是否相同。隻有屬于同一個運作時包的類才能互相通路包可見(即預設通路級别)的類和類成員。這樣的限制能避免使用者自定義的類冒充核心類庫的類去通路核心類庫的包可見成員。
假設使用者自定義了一個類:java.lang.Spy,并由使用者自定義的類加載器加載,由于java.lang.Spy和核心類庫java.lang.*由不同的加載器加載, 是以他們屬于不同的運作時包,是以java.lang.Spy不能通路核心類庫java.lang包中的可見成員。
我們下一篇來着重說下如何來實作一個自定義的類加載器
開開心心編碼,快快樂樂生活。