ClassLoader引發(fā)的類型轉(zhuǎn)換異常(轉(zhuǎn)載)

轉(zhuǎn)自:http://blog.csdn.net/wangchengsi/article/details/2110647


Java的類型轉(zhuǎn)換異常(ClassCastException),恐怕是開發(fā)中最常見的異常之一,比如你把一個(gè)本身為String的對象強(qiáng)行轉(zhuǎn)換成List時(shí),就會拋出此異常。當(dāng)然,一般情況下這種錯(cuò)誤很容易就從異常信息中發(fā)現(xiàn)原因并糾正,通常對于此類問題我們的想法就是:class文件相同,即字節(jié)碼相同,那么實(shí)例化產(chǎn)生的對象肯定也會相同類型。但是,存在一些情況,會發(fā)生形如“把class1轉(zhuǎn)換成class1卻拋出類型轉(zhuǎn)換異?!钡那闆r

先看一個(gè)例子,包含三個(gè)源文件:MainClass,ClassOne,ClassTwo 。MainClass是程序入口,另外兩個(gè)類用于測試,代碼很簡單,如下

ClassOne.java

-----------------------------------------------

package test.jboss.jmx.classCastEx;

Class ClassOne{

public void doTest(Object obj){

ClassTwo c2 = (ClassTwo)obj;

}

}



ClassTwo只是一個(gè)空類

------------------------------------------------

package test.jboss.jmx.classCastEx;

Class ClassTwo{

}




MainClass.java

------------------------------------------------

public class MainClass {

/**

* @param args

* @throws MalformedURLException

* @throws ClassNotFoundException

* @throws IllegalAccessException

* @throws InstantiationException

* @throws NoSuchMethodException

* @throws InvocationTargetException

* @throws SecurityException

* @throws IllegalArgumentException

*/

public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, SecurityException, InvocationTargetException, NoSuchMethodException {

// ClassOne 和 ClassTwo打包到一個(gè)jar中,名為“test.jar”,放在 MainClass 所在項(xiàng)目的根目錄下

// 注意,ClassOne 和 ClassTwo 不能和MainClass放在一個(gè)項(xiàng)目中,后面 有解釋

// 自定義一個(gè)類加載器,從外部導(dǎo)入ClassOne 和 ClassTwo

File jar = new File("test.jar");

System.out.println(jar.exists());

URL[] url = {jar.toURL()};

System.out.println(url[0]);

URLClassLoader ucl1 = new URLClassLoader(url);

Class classTwo = ucl1.loadClass("test.jboss.jmx.classCastEx.ClassTwo");

//查看是否成功地利用反射機(jī)制,將ClassTwo導(dǎo)入進(jìn)來,并顯示其在VM中的hash碼

// getClassLoader()正常情況返回classTwo的類加載器,也就是上面的 ucl1 ,如果不是,則有問題

System.out.println("hash of layout1:"+classTwo.hashCode()+"ucl "+classTwo.getClassLoader());

//創(chuàng)建一個(gè)ClassTwo的實(shí)例

Object c2_obj = classTwo.newInstance();

// 同理,用另一個(gè)類加載器(ucl2)載入ClassOne

File jar2 = new File("test.jar");

System.out.println(jar2.exists());

URL[] url2 = {jar2.toURL()};

URLClassLoader ucl2 = new URLClassLoader(url2);

Class classOne = ucl2.loadClass("test.jboss.jmx.classCastEx.ClassOne");

Object c1_obj = classOne.newInstance();

//利用反射,調(diào)用ClassOne的 doTest 方法,將上面創(chuàng)建的 c2_obj 作為方法的參數(shù)

Class[] type = {Object.class};

Method m = classOne.getMethod("doTest", type);

Object[] para = {c2_obj};

m.invoke(c1_obj, para);

}


如果按固有的思維,則 c2_obj 傳入 doTest方法后,執(zhí)行 ClassTwo c2 = (ClassTwo)obj; 是沒問題的,但是實(shí)際運(yùn)行則會拋出 ClassCastException ,并明確的告訴你 “ClassTwo c2 = (ClassTwo)obj”有問題,為什么呢?

錯(cuò)誤信息:

java.lang.reflect.InvocationTargetException

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:606)

at MainClass.main(MainClass.java:30)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:606)

at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

Caused by: java.lang.ClassCastException: com.test.ClassTwo cannot be cast to com.test.ClassTwo

at com.test.ClassOne.doTest(ClassOne.java:8)

... 10 more


原因在于使用了不同的類加載器載入各個(gè)類。其中,main函數(shù)中,ucl1載入的是ClassTwo,而ucl2在載入ClassOne時(shí),由于ClassOne內(nèi)部引用了ClassTwo,ucl1會把ClassTwo也一起加載進(jìn)來,這樣VM就有了兩個(gè)ClassTwo,分別對應(yīng)不同的類加載器。對于ClassOne.doTest() 中的“ ClassTwo c2 = (ClassTwo)obj”這句代碼,c2 的類型全稱是“ucl2加載的test.jboss.jmx.classCastEx.ClassOne”

現(xiàn)在我們應(yīng)該明白了,之所以會有類型轉(zhuǎn)換異常,是由于類在VM中的簽名不僅僅是類的完整包名,還包括載入它的類加載器。上述例子中,由ucl1加載的ClassTwo,作為參數(shù)傳入由ucl2加載的ClassOne.doTest() 中,自然就與 c2 的類型不符合了,導(dǎo)致無法轉(zhuǎn)換

也許你一般不會用這種“古怪”的方式加載類,通常我們都是把需要的外部類寫入項(xiàng)目的classpath,現(xiàn)在的IDE也提供非常方便的手段導(dǎo)入外部類。但是想象一下在應(yīng)用服務(wù)器容器中,你部署的多個(gè)應(yīng)用都可能共享某個(gè)jar庫的類實(shí)例。當(dāng)重新部署包含該jar的應(yīng)用時(shí),所有相關(guān)的應(yīng)用都必須刷新一遍,否則很容易出現(xiàn)上述問題

PS:本文參考了《JBOSS 4.0 標(biāo)準(zhǔn)教材》中的內(nèi)容,書中提供了相應(yīng)源碼解釋這個(gè)問題,不過比較繁瑣,上面的代碼是我簡化過的,在我的機(jī)子上試驗(yàn)成功。請不要將ClassOne 和 ClassTwo? 與MainClass放在一個(gè)項(xiàng)目中,那樣在運(yùn)行之前就會預(yù)先加載項(xiàng)目中所有的類,等于ClassOne 和 ClassTwo都由VM先加載了,就不會出現(xiàn)預(yù)期的轉(zhuǎn)換異常了??梢詫lassOne 和 ClassTwo在另一個(gè)項(xiàng)目中編寫然后打包,放到MainClass所在項(xiàng)目的根目錄

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1 基本信息 每個(gè)開發(fā)人員對java.lang.ClassNotFoundExcetpion這個(gè)異??隙ǘ疾荒吧?,...
    java小菜鳥閱讀 2,686評論 0 15
  • 1、運(yùn)行環(huán)境 主機(jī)IP 主機(jī)名 2、配置主機(jī)名(分別在五臺機(jī)器上執(zhí)行) hostname +主機(jī)名例如: h...
    獻(xiàn)給記性不好的自己閱讀 3,778評論 0 6
  • 多態(tài) 任何域的訪問操作都將有編譯器解析,如果某個(gè)方法是靜態(tài)的,它的行為就不具有多態(tài)性 java默認(rèn)對象的銷毀順序與...
    yueyue_projects閱讀 1,088評論 0 1
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,506評論 19 139
  • 刪除你的應(yīng)用程序 來自蘋果官網(wǎng)!??! 如果您已經(jīng)創(chuàng)建在iTunes Connect的應(yīng)用程序,你不再需要管理,您可...
    光明程輝閱讀 364評論 0 1

友情鏈接更多精彩內(nèi)容