轉(zhuǎn)自:http://blog.csdn.net/wangchengsi/article/details/2110647
Java的類型轉(zhuǎn)換異常(ClassCastException),恐怕是開發(fā)中最常見的異常之一剑肯,比如你把一個本身為String的對象強行轉(zhuǎn)換成List時施无,就會拋出此異常。當然钻哩,一般情況下這種錯誤很容易就從異常信息中發(fā)現(xiàn)原因并糾正,通常對于此類問題我們的想法就是:class文件相同,即字節(jié)碼相同官研,那么實例化產(chǎn)生的對象肯定也會相同類型蓬痒。但是泻骤,存在一些情況,會發(fā)生形如“把class1轉(zhuǎn)換成class1卻拋出類型轉(zhuǎn)換異澄嗌荩”的情況
先看一個例子狱掂,包含三個源文件:MainClass,ClassOne亲轨,ClassTwo 趋惨。MainClass是程序入口,另外兩個類用于測試惦蚊,代碼很簡單器虾,如下
ClassOne.java
-----------------------------------------------
package test.jboss.jmx.classCastEx;
Class ClassOne{
public void doTest(Object obj){
ClassTwo c2 = (ClassTwo)obj;
}
}
ClassTwo只是一個空類
------------------------------------------------
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打包到一個jar中,名為“test.jar”蹦锋,放在 MainClass 所在項目的根目錄下
// 注意兆沙,ClassOne 和 ClassTwo 不能和MainClass放在一個項目中,后面 有解釋
// 自定義一個類加載器莉掂,從外部導入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");
//查看是否成功地利用反射機制葛圃,將ClassTwo導入進來,并顯示其在VM中的hash碼
// getClassLoader()正常情況返回classTwo的類加載器,也就是上面的 ucl1 库正,如果不是曲楚,則有問題
System.out.println("hash of layout1:"+classTwo.hashCode()+"ucl "+classTwo.getClassLoader());
//創(chuàng)建一個ClassTwo的實例
Object c2_obj = classTwo.newInstance();
// 同理,用另一個類加載器(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; 是沒問題的载迄,但是實際運行則會拋出 ClassCastException ,并明確的告訴你 “ClassTwo c2 = (ClassTwo)obj”有問題抡蛙,為什么呢护昧?
錯誤信息:
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
原因在于使用了不同的類加載器載入各個類。其中粗截,main函數(shù)中惋耙,ucl1載入的是ClassTwo,而ucl2在載入ClassOne時熊昌,由于ClassOne內(nèi)部引用了ClassTwo绽榛,ucl1會把ClassTwo也一起加載進來,這樣VM就有了兩個ClassTwo婿屹,分別對應不同的類加載器灭美。對于ClassOne.doTest() 中的“ ClassTwo c2 = (ClassTwo)obj”這句代碼,c2 的類型全稱是“ucl2加載的test.jboss.jmx.classCastEx.ClassOne”
現(xiàn)在我們應該明白了昂利,之所以會有類型轉(zhuǎn)換異常届腐,是由于類在VM中的簽名不僅僅是類的完整包名,還包括載入它的類加載器蜂奸。上述例子中犁苏,由ucl1加載的ClassTwo,作為參數(shù)傳入由ucl2加載的ClassOne.doTest() 中扩所,自然就與 c2 的類型不符合了围详,導致無法轉(zhuǎn)換
也許你一般不會用這種“古怪”的方式加載類,通常我們都是把需要的外部類寫入項目的classpath祖屏,現(xiàn)在的IDE也提供非常方便的手段導入外部類助赞。但是想象一下在應用服務器容器中,你部署的多個應用都可能共享某個jar庫的類實例赐劣。當重新部署包含該jar的應用時嫉拐,所有相關的應用都必須刷新一遍,否則很容易出現(xiàn)上述問題
PS:本文參考了《JBOSS 4.0 標準教材》中的內(nèi)容魁兼,書中提供了相應源碼解釋這個問題,不過比較繁瑣,上面的代碼是我簡化過的咐汞,在我的機子上試驗成功盖呼。請不要將ClassOne 和 ClassTwo? 與MainClass放在一個項目中,那樣在運行之前就會預先加載項目中所有的類化撕,等于ClassOne 和 ClassTwo都由VM先加載了几晤,就不會出現(xiàn)預期的轉(zhuǎn)換異常了≈惨酰可以將ClassOne 和 ClassTwo在另一個項目中編寫然后打包蟹瘾,放到MainClass所在項目的根目錄