問
:
調用
Class.forName()
與
ClassLoader.loadClass()
的區別在什么地方
?
答
:
這兩方法都是通過一個給定的類名去定位和加載這個類名對應的
java.long.Class
類對象
.
盡管如此
,
它們的在行為方式上還是有區別的
.
?????????
用哪個
java.lang.ClassLoader
進行加載
?????????
返回的
Class
對象是否被初始化
Class.forName(String)
方法(只有一個參數), 使用調用者的類加載器來加載, 也就是用加載了調用forName方法的代碼的那個類加載器. 相應的, ClassLoader.loadClass()方法是一個實例方法(非靜態方法), 調用時需要自己指定類加載器, 那么這個類加載器就可能是也可能不是加載調用代碼的類加載器. 如果用特定的類加載器來加載類在你的設計中占有比較重要的地位, 你就應該調用ClassLoader.loadClass(String)方法或Class.forName(String, boolean, ClassLoader)方法.
???
另外, Class.forName()方法對加載的類對象進行初始化. 可見的效果就是類中靜態初始化段及字節碼中對所有靜態成員的初始工作的執行(這個過程在類的所有父類中遞歸地調用). 這點就與ClassLoader.loadClass()不同. ClassLoader.loadClass()加載的類對象是在第一次被調用時才進行初始化的.
???
你可以利用上述的差異. 比如,要加載一個靜態初始化開銷很大的類, 你就可以選擇提前加載該類(以確保它在classpath下), 但不進行初始化, 直到第一次使用該類的域或方法時才進行初始化.
???
最常用的是Class.forName(String, boolean, ClassLoader). 設置第二個參數為false即推遲初始化, 第三個參數指定要用來進行加載的類加載器. 我建議為了最大的靈活性使用這個方法.
類初始化錯誤是難處理的
???
成功地加載了類, 并不意味著就不會有其它問題. 靜態初始化代碼可以拋出異常, 異常被包裝到java.long.ExceptionInInitializerError的實例中. 異常拋出后, 這個類將不可用. 這樣, 如果你需要在代碼中處理這些錯誤, 你就應該調用進行初始化的Class.forName()方法.
???
但進一步說, 如果你要處理ExceptionInInitializerError并試圖從錯誤中恢復, 很可能不如你想象的那樣正常工作. 請看下面的示例代碼:
public
?
class
?Main
{
????
public
?
static
?
void
?main?(String?[]?args)?
throws
?Exception
????{
????????
for
?(
int
?repeat?=?0;?repeat?<?3;?++?repeat)
????????{
????????????
try
????????????{
????????????????
//?"Real"?name?for?X?is?outer?class?name+$+nested?class?name:
????????????????Class.forName?("Main$X");
????????????}
????????????
catch
?(Throwable?t)
????????????{
????????????????System.out.println?("load?attempt?#"?+?repeat?+?":");
????????????????t.printStackTrace?(System.out);
????????????}
????????}
????}
????
private
?
static
?
class
?X
????{
????????
static
????????{
????????????
if
?(++?s_count?==?1)
????????????????
throw
?
new
?RuntimeException?("failing?static?initializer");
????????}
????????
????}?
//?End?of?nested?class
????
private
?
static
?
int
?s_count;
}?
//?End?of?class
???
上面的代碼3次嘗試加載一個內部類X, 即便是X的靜態初始化只在每一次加載時失敗, 這3次加載都拋出了異常.
>java Main
load attempt #0:
java.lang.ExceptionInInitializerError
????????at java.lang.Class.forName0(Native Method)
????????at java.lang.Class.forName(Class.java:140)
????????at Main.main(Main.java:17)
Caused by: java.lang.RuntimeException: failing static initializer...
????????at Main$X.<clinit>(Main.java:40)
????????... 3 more
load attempt #1:
java.lang.NoClassDefFoundError
????????at java.lang.Class.forName0(Native Method)
????????at java.lang.Class.forName(Class.java:140)
????????at Main.main(Main.java:17)
load attempt #2:
java.lang.NoClassDefFoundError
????????at java.lang.Class.forName0(Native Method)
????????at java.lang.Class.forName(Class.java:140)
????????at Main.main(Main.java:17)
???
有點令人吃驚的時, 在第2, 3次進行類加載時, 拋出的異常竟然是java.lang.NoClassDefFoundError. 這里發生的事情是, 第一次加載后(在進行初始化之前), JVM發現X已經被加載, 而這個X的類實例在加載它的類加載器被垃圾回收之前是不會被卸載的. 所以這之后的對Class.forName()的調用時, JVM不會再嘗試進行初始化的工作, 但是, 更令人不解的是, 拋出一個NoClassDefFoundError.
???
卸載這樣的類的方法是丟棄原來加載該類的類加載器實例并重新創建一個. 當然, 這只能是在你使用了Class.forName(String, boolean, ClassLoader)這個3參數的方法的時候才能辦到.
隱藏的
Class.forName()
方法
???
你一定用過Java中X.class的語法去獲取一個在編譯器就知道名字的類對象實例. 在字節碼的層次上, 這一點是如何實現的就不被人熟知了. 不同的編譯器有不同的實例細節, 但共同點是, 所有編譯器所相應產生的代碼都是調用的Class.forName(String)這一個參數的方法. 比如J2SE 1.4.1的javac就把Class cls = X.class; 翻譯成如下等價的形式:
?
????????
//?This?is?how?"Class?cls?=?X.class"?is?transformed:
????????
if
?(
class
$Main$X?==?
null
)
????????{
????????????
class
$Main$X?=?
class
$?("Main$X");
????????}
????????Class?cls?=?
class
$Main$X;
????
????
static
?Class?
class
$?(String?s)
????{
????????
try
????????{
????????????
return
?Class.forName?(s);
????????}
????????
catch
?(ClassNotFoundException?e)
????????{
????????????
throw
?
new
?NoClassDefFoundError?(e.getMessage());
????????}
????}
????
static
?Class?
class
$Main$X;?
//?A?synthetic?field?created?by?the?compiler
跟
Sun
的
javac
開個玩笑
從上面的例子你可以看到, 編譯器調用Class.forName()方法加載類對象, 并將其緩存到一個包內可見的靜態變量中. 這種令人費解的實現方式的可能是因為在早期版本的Java中, 這種X.class的語法還未被支持, so the feature was added on top of the Java 1.0 byte-code instruction set.(???)
利用這一點, 你可以在編譯器的開銷上做一些有趣的事情. 用J2SE 1.3.1編譯下面的代碼片段:
public
?
class
?Main
{
????
public
?
static
?
void
?main?(String?[]?args)?
throws
?Exception
????{
????????System.out.println?("String?class:?"?+?String.
class
);
????????
class
$java$lang$String?=?
int
.
class
;
????????System.out.println?("String?class:?"?+?String.
class
);
????}
????
????
static
?Class?
class
$java$lang$String;
}?
//?End?of?class
運行它, 你會得到下面這個很荒謬的輸出結果:
>java Main
String class: class java.lang.String
String class: int
在J2SE 1.4.1中, 上面的代碼將不能被編譯通過, 但你仍然可以用反射的方式戲弄它:
public
?
static
?
void
?main?(String?[]?args)?
throws
?Exception
????{
????????System.out.println?("String?class:?"?+?String.
class
);
????????Main.
class
.getDeclaredField?("class$java$lang$String").set?(
null
,?
int
.
class
);
????????System.out.println?("String?class:?"?+?String.
class
);
????}
?
???
綜上所述, 下次你再調用Class.forName()方法時, 你應該知道它的局限性可選的替代方案了.
?
?