`

ClassLoader引发的类型转换异常

    博客分类:
  • JVM
阅读更多

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只是一个空类

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

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”有问题,为什么呢?

原因在于使用了不同的类加载器载入各个类。其中,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所在项目的根目录。

 

http://blog.csdn.net/wangchengsi/archive/2008/02/21/2110647.aspx

分享到:
评论

相关推荐

    理解Java ClassLoader机制

    同时,当遇到“双亲委派模型”(Parent Delegation Model)引发的问题,如类加载异常时,了解ClassLoader的运作方式可以更快定位和解决问题。 总的来说,Java ClassLoader机制是Java平台的核心特性之一,它使得程序...

    定义ClassLoader调用外部jar包

    自定义ClassLoader可能会引发安全异常,因为它可以加载不受信任的代码。因此,在生产环境中,确保对加载的类进行适当的权限检查是必要的。此外,为了保证多线程环境下的正确性,可能需要同步加载过程。 总之,...

    ClassLoader 深入解析

    总之,理解ClassLoader的工作原理和机制,可以帮助我们更好地管理Java应用程序的类加载,解决因类加载引发的问题,甚至实现高级功能如动态加载和热更新。对于Java开发者来说,深入研究ClassLoader是提升技能的重要一...

    java中常用的异常类型.docx

    - **类型强制转换异常 (`ClassCastException`)**:当试图将一个对象强制转换为不兼容的子类型时发生。 - **数组负下标异常 (`NegativeArraySizeException`)**:当创建数组时指定了一个负数作为长度时抛出。 - **文件...

    Java常见异常和错误总结

    - **定义**: 尝试将不符合数字格式的字符串转换为数字类型时引发的异常。 - **常见场景**: - 字符串包含非数字字符。 - **解决方法**: - 在进行转换前,先验证字符串是否仅包含数字。 - 使用正则表达式等工具进行...

    java环境报错大全下载地址

    6. **ClassCastException**:类型转换异常,当强制转换的对象不是预期类型时抛出。确保转换前的类型是兼容的。 7. **InterruptedException**:当线程在等待、睡眠或被中断时,如果接收到中断请求,会抛出此异常。...

    tomcat7启动或运行报错

    如果配置不正确,可能会导致同一个类在不同的上下文中被加载,从而引发类型转换异常。 - **解决步骤**: 1. **修改Tomcat配置文件**:编辑`context.xml`文件,在`<Context>`元素下添加`...

    java异常详解

    - **`java.lang.ClassCastException`**:当试图将对象强制转换为不兼容类型时抛出。 - **`java.lang.AbstractMethodError`**:当子类没有重写抽象父类的抽象方法时抛出。 - **`java.lang.ClassCircularityError`**:...

    JBoss JMX实现架构

    - ClassCastException:当尝试将一个对象强制转换为与其实际类型不兼容的类型时,Java会抛出此异常。例如,从ArrayList中尝试获取一个URL对象并将其转换为String会导致ClassCastException,因为URL对象不能被视为...

    JVM逃逸_分析、利用与防御.pdf

    2. **类型安全**:Java的内存管理和类型检查确保了结构化内存访问、自动垃圾收集、数组边界检查、空引用检查以及类型转换的安全性。 3. **类装载器(Classloader)**:采用双亲委派模型,不同类装载器负责加载不同...

    实现对java类的递归加载

    6. **异常处理**:在递归加载过程中,可能会遇到找不到类、文件格式错误等异常情况,需要正确处理这些异常,提供有意义的错误信息。 通过理解以上知识点,我们可以创建一个能实现递归加载的类加载器,这对于处理...

    JAVA面试题总览[整理].pdf

    26. 泛型的引入主要是为了类型安全,避免强制类型转换,提高代码可读性,编译时就能发现类型错误。 27. a.hashCode()用于获取对象的哈希值,与equals()相关联,相同对象的哈希值应相同,不同对象的哈希值尽量不同。...

    超级有影响力霸气的Java面试题大全文档

    引用可以转换到接口类型或从接口类型转换,instanceof 运算符可以用来决定某对象的类是否实现了接口。 21、heap和stack有什么区别。  栈是一种线形集合,其添加和删除元素的操作应在同一段完成。栈按照后进先出的...

    Java虚拟机介绍。不错的书啊

    Java虚拟机的生命周期始于应用程序的启动,终结于程序的正常退出、异常终止或由外部因素如操作系统错误导致的进程终止。这一过程包括几个关键阶段: 1. **加载**:在此阶段,JVM负责查找并加载类的二进制数据,即....

    JVM技术培训讲座

    主要包括Bootstrap ClassLoader、Extension ClassLoader和Application ClassLoader等。 - **运行时数据区**: - **方法区**:用于存储每个类的信息(包括类的方法和字段)、静态变量、常量池以及其他类数据。 - **...

Global site tag (gtag.js) - Google Analytics