Java同父类的两个子类间属性复制的简便实现方案
现有一个包含40+属性的抽象JPA父类,以及两个继承自该父类的空实现子类,需要实现两个子类实例的互相转换。
基础类定义如下:
public abstract class ParentClass{ // ... 共40+个带JPA注解的属性 } public class ChildClassA extends ParentClass{} public class ChildClassB extends ParentClass{}
直接通过类型强转的方式无法实现转换,运行时会抛出ClassCastException:
public ChildClassA to(){ return (ChildClassA) ((ParentClass) this); }
该方案不可行的根本原因:Java引用类型强转仅做继承链的类型校验,不会修改对象的实际运行时类型。如果当前实例本身是
ChildClassB类型,即使先向上转型为父类引用,也无法向下强转为ChildClassA。
已尝试的方案均存在明显缺陷:
- 手动编写拷贝构造函数逐属性赋值:40余个属性逐一编写get/set赋值逻辑工作量大,后续父类字段变更需要同步修改多处赋值代码,维护成本极高。
- 序列化/反序列化方案(XML、JSON等):这类方案多用于同类型对象克隆,反序列化时会保留原对象的实际类型信息,跨子类转换时同样会抛出类型转换异常,且序列化本身性能开销较大。
设计背景补充
该结构的设计原因说明:
业务中存在两个字段完全一致的数据库表,一张表已存储存量数据,另一张为空表。对应业务流程为:从原表查询行数据 -> 执行业务处理 -> 将处理后的数据写入第二张表。
为避免重复编写40+字段的实体定义,抽取了公共JPA父类存放所有字段及对应注解,两个子类分别使用@Table注解映射两张不同的数据库表(JPA规范中每个实体类只能绑定一个数据库表)。
实际业务流转逻辑:从原表查询得到ChildClassA实例,需要创建ChildClassB实例并拷贝所有属性值,完成业务处理后将ChildClassB持久化到第二张表。
根据项目依赖情况可以选择以下三种方案,均不需要手动逐字段编写赋值逻辑:
方案1:Spring BeanUtils(零额外依赖,适合Spring/Spring Boot生态项目)
如果项目本身使用Spring Boot集成JPA,直接使用Spring内置的BeanUtils工具即可,不需要引入任何第三方依赖。该工具通过反射实现同名字段、同类型属性的自动拷贝,自动跳过类型不匹配、不存在的字段,代码量极少:
// ChildClassA 转 ChildClassB 示例 public ChildClassB convertToB(ChildClassA source) { ChildClassB target = new ChildClassB(); // 注意导入的是org.springframework.beans.BeanUtils,不是Apache Commons版本 BeanUtils.copyProperties(source, target); return target; }
该方案实现简单,单次拷贝的反射开销在绝大多数业务场景下完全可以忽略,是日常开发中最常用的属性拷贝方式。
方案2:MapStruct(性能最优,适合高并发场景)
如果对转换性能有较高要求,可以使用MapStruct实现编译期生成转换代码。框架会在项目编译阶段自动生成和手动写get/set逻辑完全一致的转换实现,没有运行时反射开销,性能和原生代码一致,后续父类新增字段时不需要修改转换逻辑,自动同步:
- 引入MapStruct依赖后定义转换接口即可:
@Mapper public interface EntityConvertMapper { EntityConvertMapper INSTANCE = Mappers.getMapper(EntityConvertMapper.class); ChildClassB aToB(ChildClassA source); ChildClassA bToA(ChildClassB source); }
- 调用方式非常简单:
ChildClassB target = EntityConvertMapper.INSTANCE.aToB(sourceA);
如果存在特殊字段的映射规则,只需要通过@Mapping注解单独配置即可,通用同名字段不需要额外处理。
方案3:自定义反射实现(无任何第三方依赖)
如果项目没有引入Spring,也不想增加额外第三方依赖,可以自己实现简单的反射拷贝逻辑,遍历父类的所有字段完成赋值:
public <T extends ParentClass> T convert(ParentClass source, Class<T> targetClass) throws Exception { T target = targetClass.getDeclaredConstructor().newInstance(); // 遍历父类所有声明的字段,跳过静态字段和final字段 Field[] fields = ParentClass.class.getDeclaredFields(); for (Field field : fields) { if (Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers())) { continue; } field.setAccessible(true); field.set(target, field.get(source)); } return target; }
该方案逻辑透明,可以根据自己的业务需求调整拷贝规则,适合轻量场景使用。
内容的提问来源于stack exchange,提问作者ahrooran

