ClassLoader引发的类型转换异常

本文通过实例演示了Java中因不同类加载器导致的类型转换异常(ClassCastException)。揭示了即使类名相同,若由不同类加载器加载,也会被视为不同类型的原理。
部署运行你感兴趣的模型镜像

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所在项目的根目录 

您可能感兴趣的与本文相关的镜像

Qwen-Image

Qwen-Image

图片生成
Qwen

Qwen-Image是阿里云通义千问团队于2025年8月发布的亿参数图像生成基础模型,其最大亮点是强大的复杂文本渲染和精确图像编辑能力,能够生成包含多行、段落级中英文文本的高保真图像

### SQL 数据类型转换方法及其解决方案 #### 背景概述 在处理数据库操作时,数据类型的不一致可能导致各种运行时错误。例如,在 Oracle 数据库中修改列的数据类型可能会遇到 `ORA-01439` 错误[^1];而在 Entity Framework Core (EFCore) 中执行多表查询时,则可能出现对象类型转换失败的情况[^2]。 以下是针对 SQL 数据类型转换以及相关问题的常见解决策略: --- #### 一、SQL 数据类型转换的基础方法 ##### 1. 使用 CAST 函数 CAST 是一种标准的 SQL 函数,用于显式地将表达式的值从一种数据类型转换为另一种数据类型。 ```sql SELECT CAST('123' AS INT) AS ConvertedValue; ``` 上述语句会尝试将字符串 `'123'` 转换为整数类型[^4]。 ##### 2. 使用 CONVERT 函数 CONVERT 提供了更灵活的功能,允许指定日期格式等附加参数。 ```sql SELECT CONVERT(DATE, '2023-10-05', 120); ``` 此示例展示了如何将字符串转换为 DATE 类型,并指定了输入格式为 ISO8601 格式。 --- #### 二、特定场景下的类型转换问题及解决方案 ##### 1. 修改 Oracle 列的数据类型引发 ORA-01439 错误 当试图更改 Oracle 表中的某一列数据类型时,如果该列包含非 NULL 值,则会出现 `ORA-01439: column to be modified must be empty to change datatype` 错误。 **解决方案:** - 创建临时列并迁移现有数据; - 删除原始列后再重新创建目标列; - 将数据迁回新列。 具体实现如下: ```sql -- 添加临时列 ALTER TABLE my_table ADD temp_column VARCHAR2(255); -- 迁移数据 UPDATE my_table SET temp_column = original_column; -- 删除旧列 ALTER TABLE my_table DROP COLUMN original_column; -- 新建目标列 ALTER TABLE my_table ADD original_column NEW_DATATYPE; -- 恢复数据 UPDATE my_table SET original_column = temp_column; -- 清理临时列 ALTER TABLE my_table DROP COLUMN temp_column; ``` ##### 2. EFCore 多表查询中的类型转换错误 在使用 EFCore 执行复杂查询时,可能因为模型定义与实际数据库字段之间的类型差异而触发异常,如 `Unable to cast object of type 'System.Data.SqlTypes.SqlString' to type 'System.Data.SqlTypes.SqlGuid'`。 **解决方案:** - **验证实体映射一致性**:确保 C# 实体类属性与其对应数据库字段的数据类型完全匹配。 - **更新驱动程序**:考虑升级或更换底层 ADO.NET 驱动程序版本,例如由 System.Data.SqlClient 替换为 Microsoft.Data.SqlClient[^3]。 - **强制类型转换**:通过 LINQ 查询手动调整返回结果集结构,避免隐式转换带来的风险。 示例代码片段展示如何修正潜在冲突: ```csharp var result = context.MyEntities .Where(e => e.Id != null && e.Name.Contains("example")) .Select(e => new { Id = Guid.Parse(e.StringId), // 显式解析 GUID 字符串 Name = e.Name, CreatedAt = DateTime.SpecifyKind((DateTime)e.CreatedDate, DateTimeKind.Utc) }) .ToList(); ``` ##### 3. JDBC 配置不当引起的连接问题 对于基于 Java 的大数据框架(如 Apache Spark),若未正确加载相应的 JDBC 驱动文件或将路径加入 ClassLoader,则可能发生未知异常。因此建议始终确认依赖项已妥善部署于项目环境中。 --- ### 总结 无论是传统关系型数据库还是现代 ORM 工具链路下,合理运用内置函数完成必要的数据转型至关重要。与此同时,面对复杂的跨平台交互需求时还需兼顾兼容性和性能优化考量。
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值