class.forName()除了將類的.class文件加載到jvm中之外,還會對類進行解釋,執行類中的static塊。
classLoader只幹一件事情,就是將.class文件加載到jvm中,不會執行static中的內容,只有在newInstance纔會去執行static塊。
Class.forName(name, initialize, loader)帶參函數也可控制是否加載static塊。並且只有調用了newInstance()方法採用調用構造函數,創建類的對象。
先了解下類加載機制,
5個過程。
a.加載:java類運行時候會生成一個class字節碼文件,加載的過程就是去我們的操作系統尋找這個class文件。而獲取.class文件的方式,可以通過jar包、war包、網絡中獲取、JSP文件生成等方式。
b.鏈接:又分爲3個小過程。
1.驗證:驗證被加載後的類是否有正確的結構,類數據是否會符合虛擬機的要求,確保不會危害虛擬機安全。
2.準備:爲類的靜態變量(static filed)在方法區分配內存,並賦默認初值(0值或null值)。如static int maxNum = 10;
靜態變量maxNum 就會在準備階段被賦默認值0;而對於一般的成員變量是在類實例化時候,隨對象一起分配在堆內存中。靜態常量(static final filed)會在準備階段賦程序設定的初值,如static final int maxNum = 50; 靜態常量a就會在準備階段被直接賦值爲50,對於靜態變量,這個操作是在初始化階段進行的。
3.解析:解析是將符號引用替換爲直接引用,解析動作針對類或接口,字段,類或接口的方法進行解析。
首先是用類加載器加載這個類。在加載的過程中逐步解析類中的字段和方法。
符號引用是以字面量的實形式明確定義在常量池中,直接引用是指向目標的指針,或者相對偏移量。
c.初始化:類初始化是類加載的最後一步,除了加載階段,用戶可以通過自定義的類加載器參與,其他階段都完全由虛擬機主導和控制。主要工作是爲靜態變量賦程序設定的初值。如static int maxNum = 10;在準備階段,a被賦默認值0,在初始化階段就會被賦值爲10。
當初始化一個類的時候,如果發現其父類沒有進行過初始化,則首先觸發父類初始化。
d.使用:在需要使用的地方調用
e.卸載:使用完了,java虛擬機進行清理。
類的加載的時機,有以下5中情況
1 遇到new、getstatic、putstatic或invokestatic這四條字節碼指令
2 使用java.lang.reflect包的方法對類進行反射調用的時候,如果類沒有進行過初始化,則需要先觸發其初始化。
3 當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化
4 當虛擬機啓動時,用戶需要指定一個要執行的主類(包含main()方法的那個類),虛擬機會先初始化這個主類。
5當使用jdk1.7動態語言支持時,如果一個java.lang.invoke.MethodHandle實例最後的解析結果 REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,並且這個方法句柄所對應的類沒有進行過初始化,則會先觸發其初始化。
上面介紹了類加載的幾個過程,再具體看下Class.forName和classloader的區別。用代碼演示。
先定義一個類
package com.test.a;
public class User {
private static int a = 5;
private static int b;
private static int c = testC();
static {
System.out.println("執行靜態代碼塊");
System.out.println("執行靜態變量a="+a);
}
public static void testA() {
System.out.println("執行靜態代方法testA");
}
public static void testB() {
b = 10;
System.out.println("輸出靜態變量b="+b);
System.out.println("執行靜態代方法testB");
}
public static int testC() {
System.out.println("執行靜態代方法testC");
return 20;
}
}
分別使用Class.forName和classloader加載,查看結果
package com.test.a;
public class TestClassLoad {
public static void main(String[] args) {
try {
//Class.forName加載
String userPath = "com.test.a.User";
Class<?> forName = Class.forName(userPath);
System.out.println("-------------------forName加載--------------------"+forName);
//classLoader加載
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
classLoader.loadClass(userPath);
System.out.println("-------------------classLoader加載--------------------");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
可以看到classloader只是做了加載,沒有對類初始化,而Class.forName加載了靜態變量和靜態代碼塊,但是並沒有加載形態方法。只有在靜態變量調用了靜態方法賦值的情況下才會加載靜態方法。
看下Class.forName源碼分析,他實際上也是調用了classloader去加載,但因爲他的第二個參數指定了true,所以會去初始化類
要想不初始化,調用他的重載方法Class.forName(userPath,false,ClassLoader.getSystemClassLoader());將第二個參數設置爲false,指Class被加載後是不是必須被初始化。