sharajava

          類的動態(tài)加載

          : 調(diào)用 Class.forName() ClassLoader.loadClass() 的區(qū)別在什么地方 ?

          : 這兩方法都是通過一個給定的類名去定位和加載這個類名對應(yīng)的 java.long.Class 類對象 . 盡管如此 , 它們的在行為方式上還是有區(qū)別的 .

          ????????? 用哪個 java.lang.ClassLoader 進(jìn)行加載

          ????????? 返回的 Class 對象是否被初始化

          Class.forName(String) 方法(只有一個參數(shù)), 使用調(diào)用者的類加載器來加載, 也就是用加載了調(diào)用forName方法的代碼的那個類加載器. 相應(yīng)的, ClassLoader.loadClass()方法是一個實例方法(非靜態(tài)方法), 調(diào)用時需要自己指定類加載器, 那么這個類加載器就可能是也可能不是加載調(diào)用代碼的類加載器. 如果用特定的類加載器來加載類在你的設(shè)計中占有比較重要的地位, 你就應(yīng)該調(diào)用ClassLoader.loadClass(String)方法或Class.forName(String, boolean, ClassLoader)方法.

          ??? 另外, Class.forName()方法對加載的類對象進(jìn)行初始化. 可見的效果就是類中靜態(tài)初始化段及字節(jié)碼中對所有靜態(tài)成員的初始工作的執(zhí)行(這個過程在類的所有父類中遞歸地調(diào)用). 這點就與ClassLoader.loadClass()不同. ClassLoader.loadClass()加載的類對象是在第一次被調(diào)用時才進(jìn)行初始化的.

          ??? 你可以利用上述的差異. 比如,要加載一個靜態(tài)初始化開銷很大的類, 你就可以選擇提前加載該類(以確保它在classpath), 但不進(jìn)行初始化, 直到第一次使用該類的域或方法時才進(jìn)行初始化.

          ??? 最常用的是Class.forName(String, boolean, ClassLoader). 設(shè)置第二個參數(shù)為false即推遲初始化, 第三個參數(shù)指定要用來進(jìn)行加載的類加載器. 我建議為了最大的靈活性使用這個方法.

          類初始化錯誤是難處理的

          ??? 成功地加載了類, 并不意味著就不會有其它問題. 靜態(tài)初始化代碼可以拋出異常, 異常被包裝到java.long.ExceptionInInitializerError的實例中. 異常拋出后, 這個類將不可用. 這樣, 如果你需要在代碼中處理這些錯誤, 你就應(yīng)該調(diào)用進(jìn)行初始化的Class.forName()方法.

          ??? 但進(jìn)一步說, 如果你要處理ExceptionInInitializerError并試圖從錯誤中恢復(fù), 很可能不如你想象的那樣正常工作. 請看下面的示例代碼:


          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次嘗試加載一個內(nèi)部類X, 即便是X的靜態(tài)初始化只在每一次加載時失敗, 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次進(jìn)行類加載時, 拋出的異常竟然是java.lang.NoClassDefFoundError. 這里發(fā)生的事情是, 第一次加載后(在進(jìn)行初始化之前), JVM發(fā)現(xiàn)X已經(jīng)被加載, 而這個X的類實例在加載它的類加載器被垃圾回收之前是不會被卸載的. 所以這之后的對Class.forName()的調(diào)用時, JVM不會再嘗試進(jìn)行初始化的工作, 但是, 更令人不解的是, 拋出一個NoClassDefFoundError.

          ??? 卸載這樣的類的方法是丟棄原來加載該類的類加載器實例并重新創(chuàng)建一個. 當(dāng)然, 這只能是在你使用了Class.forName(String, boolean, ClassLoader)這個3參數(shù)的方法的時候才能辦到.

          隱藏的 Class.forName() 方法

          ??? 你一定用過JavaX.class的語法去獲取一個在編譯器就知道名字的類對象實例. 在字節(jié)碼的層次上, 這一點是如何實現(xiàn)的就不被人熟知了. 不同的編譯器有不同的實例細(xì)節(jié), 但共同點是, 所有編譯器所相應(yīng)產(chǎn)生的代碼都是調(diào)用的Class.forName(String)這一個參數(shù)的方法. 比如J2SE 1.4.1javac就把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 開個玩笑

          從上面的例子你可以看到, 編譯器調(diào)用Class.forName()方法加載類對象, 并將其緩存到一個包內(nèi)可見的靜態(tài)變量中. 這種令人費解的實現(xiàn)方式的可能是因為在早期版本的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

          運(yùn)行它, 你會得到下面這個很荒謬的輸出結(jié)果:

          >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 );
          ????}

          ?

          ??? 綜上所述, 下次你再調(diào)用Class.forName()方法時, 你應(yīng)該知道它的局限性可選的替代方案了.

          ?

          ?

          posted on 2006-07-27 09:06 sharajava 閱讀(2150) 評論(0)  編輯  收藏 所屬分類: 深入Java底層


          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 华宁县| 洛隆县| 淮阳县| 安化县| 独山县| 清苑县| 定南县| 太康县| 上杭县| 太谷县| 洞头县| 永清县| 巴楚县| 申扎县| 周宁县| 塔城市| 齐河县| 东丽区| 大邑县| 衢州市| 正定县| 邹城市| 马边| 黑河市| 廉江市| 陇南市| 邢台县| 武城县| 丹棱县| 丁青县| 大姚县| 宁陕县| 那曲县| 大英县| 新安县| 固阳县| 桃园市| 横山县| 武隆县| 如东县| 辽阳市|