如何通过泛型或Java8 Function类消除Java方法重复代码
现有泛型方法存在大量重复分支逻辑,原始代码如下:
private <Y> void meth( MyObj ds, MultiValueMap<String, List> mvm, Class<Y> data) { if(data.isAssignableFrom(Employee.class)) { for (Employee rd : (List<Employee>) mvm.get(0).get(1)) { for (String cName : (List<String>) mvm.get(0).get(0)) { ds.setCellValue((String)rd.getDataElement(cName)); } } } if(data.isAssignableFrom(Department.class)) { for (Department rd : (List<Department>) mvm.get(0).get(1)) { for (String cName : (List<String>) mvm.get(0).get(0)) { ds.setCellValue((String)rd.getDataElement(cName)); } } } // 其余10个左右逻辑高度相似的if分支 }
各分支仅处理的业务实体类型(Employee、Department等)有区别,遍历MultiValueMap取值、调用setCellValue与getDataElement的核心逻辑完全重复,纠结选择Java 8 Function传参方案,还是优化泛型实现来消除冗余。
不需要二选一,两种方案是互补关系,单独使用任意一种都有明显短板:
- 仅优化泛型:现有泛型参数
Y没有任何类型约束,编译器根本不知道Y类型存在getDataElement方法,才会需要每个分支单独做类型强转、写重复逻辑,如果不配合公共行为约束,光调整泛型写法根本解决不了重复问题。 - 仅传Function参数:可以解决不同类型的方法适配问题,但缺少泛型约束的话,类型校验只能留到运行时,容易抛出类型转换异常,代码可读性也更差。
实际落地可以根据项目的代码修改权限选对应组合方案,两种方案都能完全删掉所有重复if分支,后续新增实体类型不需要修改方法内部逻辑。
方案1:泛型上界约束+公共接口(优先选)
适合场景:所有业务实体的getDataElement方法签名完全一致,且你可以修改这些实体类的代码。
- 先抽公共接口,约束所有实体的公共行为:
// 所有需要处理的业务实体统一实现该接口 public interface DataElementProvider { String getDataElement(String columnName); }
让Employee、Department等所有对应实体都实现这个接口,不需要修改实体内部的原有逻辑。
2. 重构meth方法,给泛型加上界约束,删掉所有if分支:
private <T extends DataElementProvider> void meth( MyObj ds, MultiValueMap<String, List> mvm, Class<T> entityClass) { // 公共逻辑只需要写一次,不用每个分支重复取值 List<String> columnNames = (List<String>) mvm.get(0).get(0); List<T> entityList = (List<T>) mvm.get(0).get(1); for (T entity : entityList) { for (String columnName : columnNames) { ds.setCellValue(entity.getDataElement(columnName)); } } }
这个方案类型安全等级最高,所有类型校验在编译期完成,不会出现运行时强转异常,调用方也不需要传额外参数,使用成本最低。
方案2:泛型+函数式参数(适配不能修改实体的场景)
适合场景:没法修改原有实体类代码,或者部分实体的取值逻辑不统一。
因为取值逻辑需要传入动态的列名,单参数Function不够用,用BiFunction接收实体、列名两个入参,返回对应的单元格值即可:
private <T> void meth( MyObj ds, MultiValueMap<String, List> mvm, Class<T> entityClass, BiFunction<T, String, String> valueGetter) { List<String> columnNames = (List<String>) mvm.get(0).get(0); List<T> entityList = (List<T>) mvm.get(0).get(1); for (T entity : entityList) { for (String columnName : columnNames) { ds.setCellValue(valueGetter.apply(entity, columnName)); } } }
调用时直接传入对应实体的方法引用即可,不需要写任何分支判断:
// 处理Employee meth(ds, mvm, Employee.class, Employee::getDataElement); // 处理Department meth(ds, mvm, Department.class, Department::getDataElement);
这个方案灵活度最高,不需要侵入原有实体的代码,哪怕个别实体的取值方法名不一样,只要符合入参出参规则,都可以通过传入自定义lambda适配。
额外注意
原代码里用isAssignableFrom做类型判断本身就有风险:如果传入的是对应实体的子类,会进入分支但强转可能失败,上述两种重构方案都把类型校验提前到了编译期,能规避这类运行时异常。
内容的提问来源于stack exchange,提问作者John

