一個Java類從編輯的文本檔案如: Test.java檔案,需要經過編譯器編譯、加載-連結-初始化才能被使用。其中編譯是java編譯器将Test.java檔案編譯成Test.class位元組碼檔案,這裡主要是:load-link-initialization
1.加載load
Java中類的加載是通過類加載器完成的。類加載器最終要完成的功能是定義一個java類,即把java位元組碼檔案轉換為JVM中java.lang.Class對象。
- bootstrap classloader: 啟動類加載器是由JVM原生代碼實作的,
- user-defined classloader:使用者自定義的類加載器都繼承自java.lang.ClassLoader類。
Class.forName(“SomeClass”)和ClassLoader.getSystemClassLoader().loadClass(“SomeClass”):
這裡有相關詳細連結。
這2種方式都是動态地裝載類到classpath的方法,但兩者在1).是否在裝載時初始化; 2).從哪裡被裝載;這兩點問題上有所不同。
Class.forName(“SomeClass”)
- 預設地,類在裝載時被初始化。這意味着類中靜态變量被初始化了。
- 其次,類從目前類裝載器中被裝載。當調用Class.forName()裝載JDBC驅動類時,該驅動類被裝載到你所調用的類裝載器那裡。簡言之,被裝載類被裝載到調用者的類裝載器中。
- Class.forName(className, true, currentLoader), 這是一個被重載的方法。可以選擇性地加入第二或第三個參數來改變一些行為。
ClassLoader.loadClass()
- 預設地,類在裝載時并沒有被初始化。類在classpath中被裝載,并可用;但變量隻有在被調用者調用時才被初始化。
- 該方法的一個優勢是,可以選擇裝載類到特定的類裝載器中。
package com.fqyuan.thinking;
public class Person {
public static void main(String[] args) {
ClassLoader cl1 = Thread.currentThread().getContextClassLoader();
System.out.println(cl1.toString());
ClassLoader cl2 = ClassLoader.getSystemClassLoader();
System.out.println(cl2.toString());
try {
Class<?> c1 = cl1.loadClass("com.fqyuan.thinking.ForLoadTest");
System.out.println(c1.hashCode() + " \n" + c1.getClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try {
Class<?> c2 = Class.forName("com.fqyuan.thinking.ForLoadTest");
System.out.println(c2.hashCode() + " \n" + c2.getClassLoader());
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//Running result:
sun.misc.Launcher$AppClassLoader@2a139a55
sun.misc.Launcher$AppClassLoader@2a139a55
sun.misc.Launcher$AppClassLoader@2a139a55
Static Initializer Called!!
sun.misc.Launcher$AppClassLoader@2a139a55
2.連結link
JLS有詳細解釋。
1. Verification: 驗證保證了二進制的類或接口結構上是正确的。
2. Preparation:包括建立類或接口的靜态域,并把這些域初始化到預設值。
3. Resolution:将運作時常量池中的符号引用動态解析成實際值的方式。即:保證目前類的引用被正确得找到。
3.初始化initialization
注意,這裡是Class的初始化,而不是instance對象執行個體的初始化。當Java類第一次被真正使用時,JVM負責初始化該類。實際做的工作是:
- 執行靜态代碼塊;
- 初始化靜态資料域
package fqy.iss.thinking.reuse;
import static fqy.iss.utils.Print.print;
class Insect
{
private int i = ;
protected int j;
Insect()
{
print("i = " + i + ", j = " + j);
j = ;
}
private static int x1 = printInit("static Insect.x1 initialized");
static int printInit(String s)
{
print(s);
return ;
}
}
public class Beetle extends Insect
{
private int k = printInit("Beetle.k initialized");
public Beetle()
{
print("k = " + k);
print("j = " + j);
}
private static int x2 = printInit("static Beetle.x2 initialized");
public static void main(String[] args)
{
print("Beetle constructor");
Beetle b = new Beetle();
}
}
//Running result
static Insect.x1 initialized
static Beetle.x2 initialized
Beetle constructor
i = , j =
Beetle.k initialized
k =
j =
産生上述結果分析;
- 首先按照從父類到基類的順序,類被加載。完成靜态資料域和靜态代碼塊的執行。然後類加載器ClassLoader就把Class傳回,故而出現了運作結果中的前2行。
- 然後開始執行main() 方法。main()方法中new了基類對象。實際執行順序是:先初始化父類中的執行個體方法,再執行父類構造方法;再初始化子類中的執行個體方法,然後執行字類的構造方法。