记一次函数传参导致的异常
背景
在开发过程中,为了实现逻辑复用,我们重写并抽离了一部分公共方法。这些方法通过 Feign 调用 Oms 系统,用于支持多客户导入功能。在此过程中,需要记录成功列表和错误列表,并最终返回给调用方。
问题描述
在重构代码时,为了去重,我们将 errorList
的类型从 List
改为 Set
,并在方法内部使用了以下代码:
new ArrayList<>(errorSet)
来对 errorList
进行重新赋值。然而,这种操作导致了一个严重的问题:虽然 errorSet
中的数据被正确处理了,但 errorList
在上层方法中仍然是空的。
原因分析
引用问题:
- Java 中的集合(如
List
和Set
)是引用类型。 - 使用
new ArrayList<>(errorSet)
会创建一个新的对象,生成新的内存地址和引用。 - 上层方法持有的仍然是原来的
errorList
引用,而方法内部的操作并未修改该引用,因此上层方法无法感知到错误信息的变化。
- Java 中的集合(如
IDEA 警告:
- IDEA 在代码中给出了警告,但每当回事,未及时处理。
- 警告内容可能类似于:"Reassignment of method parameter 'errorList'" 或 "Possible unintended reference modification"。
设计缺陷:
- 方法签名中直接传递了
List<OmpProductExcel> errorList
,并通过方法内部对其重新赋值。 - 这种设计容易引发误解,因为调用者通常期望传入的参数不会被重新赋值。
解决方案
- 方法签名中直接传递了
为了避免上述问题,可以采取以下改进措施:
方法一:直接操作传入的集合[最终解决方案]
不重新赋值 errorList
,而是直接对其进行操作:
private List<OmpProductExcel> batchValidCommon(
AbstractExcelListener.SimpleCheckParam<OmpProductExcel> param,
List<OmpProductExcel> targetList,
List<OmpProductExcel> errorList,
boolean customerFlag) {
Set<OmpProductExcel> errorSet = new HashSet<>();
// 填充 errorSet 的逻辑
errorSet.addAll(...);
// 直接将 errorSet 的内容添加到 errorList
errorList.addAll(errorSet);
// 返回成功列表或其他逻辑
return targetList;
}
这种方式保持了 errorList
的引用不变,确保上层方法能够感知到数据的变化。
方法二:返回新的集合
如果确实需要返回一个新的集合,可以通过方法的返回值传递,而不是直接修改传入的参数:
private List<OmpProductExcel> batchValidCommon(
AbstractExcelListener.SimpleCheckParam<OmpProductExcel> param,
List<OmpProductExcel> targetList,
boolean customerFlag) {
Set<OmpProductExcel> errorSet = new HashSet<>();
// 填充 errorSet 的逻辑
errorSet.addAll(...);
// 返回新的错误列表
return new ArrayList<>(errorSet);
}
调用方可以根据返回值获取错误列表,避免了引用问题。
方法三:使用包装类
如果需要同时返回多个结果,可以使用一个包装类:
public class ValidationResult<T> {
private List<T> successList;
private List<T> errorList;
// Getters and Setters
}
private ValidationResult<OmpProductExcel> batchValidCommon(
AbstractExcelListener.SimpleCheckParam<OmpProductExcel> param,
List<OmpProductExcel> targetList,
boolean customerFlag) {
Set<OmpProductExcel> errorSet = new HashSet<>();
// 填充 errorSet 的逻辑
errorSet.addAll(...);
ValidationResult<OmpProductExcel> result = new ValidationResult<>();
result.setSuccessList(targetList);
result.setErrorList(new ArrayList<>(errorSet));
return result;
}
这种方式更加清晰,避免了对方法参数的直接修改。
总结
引用的重要性:
- 在 Java 中,集合是引用类型,操作时需要注意引用的变化。
- 如果需要修改集合的内容,应直接操作原集合,而不是创建新的集合。
IDEA 警告的价值:
- IDEA 的警告往往是潜在问题的提示,应尽量消除所有警告。
- 如果警告无法消除,应明确其原因并添加注释说明。
方法设计原则:
- 避免在方法内部直接重新赋值传入的参数。
- 如果需要返回新的结果,优先通过返回值或包装类传递。
类似问题的预防:
- 集合去重:在使用
Set
去重时,确保正确实现了hashCode
和equals
方法。 - 引用传递:理解引用传递的机制,避免无意中创建新的对象。
- 代码审查:在代码审查阶段重点关注方法参数的使用,避免不必要的副作用。
- 集合去重:在使用