ClassLoader引发的类型转换异常(转载)

简介: 转自:http://blog.csdn.net/wangchengsi/article/details/2110647Java的类型转换异常(ClassCastException),恐怕是开发中最常见的异常之一,比如你把一个本身为String的对象强行转换成List时,就会抛出此异常。

转自:http://blog.csdn.net/wangchengsi/article/details/2110647


Java的类型转换异常(ClassCastException),恐怕是开发中最常见的异常之一,比如你把一个本身为String的对象强行转换成List时,就会抛出此异常。当然,一般情况下这种错误很容易就从异常信息中发现原因并纠正,通常对于此类问题我们的想法就是:class文件相同,即字节码相同,那么实例化产生的对象肯定也会相同类型。但是,存在一些情况,会发生形如“把class1转换成class1却抛出类型转换异常”的情况

先看一个例子,包含三个源文件: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());

//创建一个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();

//利用反射,调用ClassOne的 doTest 方法,将上面创建的 c2_obj 作为方法的参数

Class[] type = {Object.class};

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

Object[] para = {c2_obj};

m.invoke(c1_obj, para);

}


如果按固有的思维,则 c2_obj 传入 doTest方法后,执行 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函数中,ucl1载入的是ClassTwo,而ucl2在载入ClassOne时,由于ClassOne内部引用了ClassTwo,ucl1会把ClassTwo也一起加载进来,这样VM就有了两个ClassTwo,分别对应不同的类加载器。对于ClassOne.doTest() 中的“ ClassTwo c2 = (ClassTwo)obj”这句代码,c2 的类型全称是“ucl2加载的test.jboss.jmx.classCastEx.ClassOne”

现在我们应该明白了,之所以会有类型转换异常,是由于类在VM中的签名不仅仅是类的完整包名,还包括载入它的类加载器。上述例子中,由ucl1加载的ClassTwo,作为参数传入由ucl2加载的ClassOne.doTest() 中,自然就与 c2 的类型不符合了,导致无法转换

也许你一般不会用这种“古怪”的方式加载类,通常我们都是把需要的外部类写入项目的classpath,现在的IDE也提供非常方便的手段导入外部类。但是想象一下在应用服务器容器中,你部署的多个应用都可能共享某个jar库的类实例。当重新部署包含该jar的应用时,所有相关的应用都必须刷新一遍,否则很容易出现上述问题

PS:本文参考了《JBOSS 4.0 标准教材》中的内容,书中提供了相应源码解释这个问题,不过比较繁琐,上面的代码是我简化过的,在我的机子上试验成功。请不要将ClassOne 和 ClassTwo  与MainClass放在一个项目中,那样在运行之前就会预先加载项目中所有的类,等于ClassOne 和 ClassTwo都由VM先加载了,就不会出现预期的转换异常了。可以将ClassOne 和 ClassTwo在另一个项目中编写然后打包,放到MainClass所在项目的根目录

目录
相关文章
|
9月前
|
算法 编译器 C语言
【C++ 异常】C++ 标准库异常类及其应用
【C++ 异常】C++ 标准库异常类及其应用
119 0
|
9月前
|
Java UED
Java中的异常处理:捕获、声明与抛出
Java中的异常处理:捕获、声明与抛出
176 0
|
Java 编译器 开发者
java中运行时异常与编译时异常?
java中运行时异常与编译时异常?
|
3月前
|
Java Android开发
如何确定抛出`NoSuchFieldError`异常的字段
当Java程序运行时,如果尝试访问一个不存在的字段,就会抛出`NoSuchFieldError`异常。要确定引发此异常的字段,可以通过检查异常堆栈跟踪中的类名和字段名来定位问题所在。此外,确保所使用的类版本一致,避免因类文件不匹配导致的此类错误。
303 8
|
9月前
|
Java 编译器
Java一分钟之——异常分类:检查异常与运行时异常
【5月更文挑战第20天】Java异常处理分为检查异常(Checked Exceptions)和运行时异常(Unchecked Exceptions),两者在编译期处理方式不同。检查异常需捕获或声明,如`IOException`,而运行时异常如`NullPointerException`在运行时终止程序。常见问题包括不恰当的异常使用、过度捕获和忽略异常信息。避免策略包括正确区分异常类型、具体捕获和处理异常信息。示例代码展示了如何处理这两种类型的异常。理解并妥善处理异常能提升程序的健壮性和可维护性。
235 4
|
6月前
|
Java 编译器 数据库连接
|
8月前
|
Java
ClassCastException(类转换异常)可能原因和解决方法总结
ClassCastException(类转换异常)可能原因和解决方法总结
|
9月前
|
Java 开发者
Java中的受检异常和非受检异常的区别
Java中的受检异常和非受检异常的区别
|
9月前
|
安全 Java 程序员
Java中的异常Exception和捕获,自定义异常
Java中的异常Exception和捕获,自定义异常
|
9月前
|
Java
java 自定义异常类并且捕获异常输出信息
java 自定义异常类并且捕获异常输出信息

热门文章

最新文章