如何将Lombok @Data类作为参数传入公共函数实现代码复用?
这个场景我太熟了——一堆结构相似的Lombok @Data类,一堆换汤不换药的接口方法,就差个通用逻辑来收口!下面给你几个实用的实现思路,从手写泛型到工具类都有,你可以根据自己的场景选:
方案一:泛型方法 + Supplier(类型安全,推荐)
这是最直接的手写方案,核心是用泛型定义公共转换逻辑,通过Supplier传入目标类的实例化方式,既避免反射,又保证类型安全。
步骤:
- 提取公共的转换逻辑到泛型方法中
- 每个业务方法只需要传入原始数据和目标类的构造引用
示例代码:
// 假设原始数据类是RawData,业务参数类是SomeParam public class DataService { // 公共转换方法:泛型T代表目标@Data类 private <T> T convertRawData(RawData rawData, Supplier<T> targetCreator) { T target = targetCreator.get(); // 这里是你重复的转换逻辑,所有目标类通用 target.setUserId(rawData.getUserId()); target.setCreateTime(rawData.getCreateTime()); target.setAmount(rawData.getAmount()); // ... 其他相同的字段映射 return target; } // 原来的业务方法简化成这样 public DataForMSDForAdvanceRequest fetchDataMSDForAdvanceRequest(SomeParam param) { RawData rawData = getRawDataFromSource(param); // 原始数据获取逻辑(比如查DB/调接口) return convertRawData(rawData, DataForMSDForAdvanceRequest::new); } public DataForMSDForAnotherRequest fetchDataMSDForAnotherRequest(SomeParam param) { RawData rawData = getRawDataFromSource(param); return convertRawData(rawData, DataForMSDForAnotherRequest::new); } // 原始数据获取的公共方法 private RawData getRawDataFromSource(SomeParam param) { // 实际的原始数据获取逻辑 return new RawData(); } }
优点:类型安全、无反射开销、代码简洁,每个业务方法只需要一行调用。
注意:确保你的Lombok @Data类有无参构造(默认会生成,除非你手动加了@AllArgsConstructor但没加@NoArgsConstructor)。
方案二:泛型方法 + 反射(无需传入构造引用)
如果不想每个业务方法都传构造引用,可以用反射自动创建目标类实例,但要注意异常处理和性能开销。
示例代码:
public class DataService { private <T> T convertRawData(RawData rawData, Class<T> targetClass) { try { // 通过反射创建目标实例 T target = targetClass.getDeclaredConstructor().newInstance(); // 公共转换逻辑 target.setUserId(rawData.getUserId()); target.setCreateTime(rawData.getCreateTime()); // ... return target; } catch (Exception e) { throw new RuntimeException("Failed to create target DTO instance", e); } } // 业务方法调用 public DataForMSDForAdvanceRequest fetchDataMSDForAdvanceRequest(SomeParam param) { RawData rawData = getRawDataFromSource(param); return convertRawData(rawData, DataForMSDForAdvanceRequest.class); } }
优点:调用时只需要传Class对象,无需构造引用;
缺点:有反射开销,且如果目标类没有无参构造会抛出异常,类型安全性不如方案一。
方案三:模板方法模式(适合未来有扩展需求)
如果以后某个业务的转换逻辑可能有细微调整,可以用模板方法模式,把公共逻辑放在抽象类,子类负责实例化目标类。
示例代码:
// 抽象模板类 public abstract class AbstractDataFetcher<T> { public T fetch(SomeParam param) { RawData rawData = getRawData(param); T target = createTarget(); convertRawData(rawData, target); return target; } // 子类实现:创建目标DTO实例 protected abstract T createTarget(); // 公共转换逻辑(如果某子类需要调整,可以改成protected让子类重写) private void convertRawData(RawData rawData, T target) { target.setUserId(rawData.getUserId()); target.setCreateTime(rawData.getCreateTime()); // ... } private RawData getRawData(SomeParam param) { // 原始数据获取逻辑 return new RawData(); } } // 具体业务实现类 public class MSDForAdvanceFetcher extends AbstractDataFetcher<DataForMSDForAdvanceRequest> { @Override protected DataForMSDForAdvanceRequest createTarget() { return new DataForMSDForAdvanceRequest(); } } // 使用方式 public class DataService { public DataForMSDForAdvanceRequest fetchDataMSDForAdvanceRequest(SomeParam param) { return new MSDForAdvanceFetcher().fetch(param); } }
优点:扩展性好,未来如果某个业务需要修改转换逻辑,只需要重写对应的方法;
缺点:需要创建多个子类,代码量比泛型方法多。
方案四:用Bean映射工具(零手写转换逻辑,强烈推荐)
如果你的转换逻辑只是简单的字段映射(字段名一致或有规律),直接用MapStruct、ModelMapper这类工具,完全不用自己写转换代码,工具会在编译期自动生成高效的映射实现。
以MapStruct为例:
- 引入MapStruct依赖(Maven/Gradle)
- 定义映射接口:
@Mapper public interface RawDataMapper { // 单例实例,直接调用 RawDataMapper INSTANCE = Mappers.getMapper(RawDataMapper.class); // 定义每个DTO的映射方法 DataForMSDForAdvanceRequest toMSDForAdvance(RawData rawData); DataForMSDForAnotherRequest toMSDForAnother(RawData rawData); // 其他DTO的映射方法... }
- 业务方法简化:
public class DataService { public DataForMSDForAdvanceRequest fetchDataMSDForAdvanceRequest(SomeParam param) { RawData rawData = getRawDataFromSource(param); return RawDataMapper.INSTANCE.toMSDForAdvance(rawData); } }
优点:零手写转换逻辑、编译期生成代码(性能和手写一样)、支持自定义映射规则;
缺点:需要引入第三方依赖,但对于字段映射场景来说,这绝对是最省心的方案。
内容的提问来源于stack exchange,提问作者Max

