记一次函数传参导致的异常

背景

在开发过程中,为了实现逻辑复用,我们重写并抽离了一部分公共方法。这些方法通过 Feign 调用 Oms 系统,用于支持多客户导入功能。在此过程中,需要记录成功列表和错误列表,并最终返回给调用方。

问题描述

在重构代码时,为了去重,我们将 errorList 的类型从 List 改为 Set,并在方法内部使用了以下代码:

new ArrayList<>(errorSet)

来对 errorList 进行重新赋值。然而,这种操作导致了一个严重的问题:虽然 errorSet 中的数据被正确处理了,但 errorList 在上层方法中仍然是空的。

原因分析

  1. 引用问题

    • Java 中的集合(如 List 和 Set)是引用类型。
    • 使用 new ArrayList<>(errorSet) 会创建一个新的对象,生成新的内存地址和引用。
    • 上层方法持有的仍然是原来的 errorList 引用,而方法内部的操作并未修改该引用,因此上层方法无法感知到错误信息的变化。
  2. IDEA 警告

    • IDEA 在代码中给出了警告,但每当回事,未及时处理。
    • 警告内容可能类似于:"Reassignment of method parameter 'errorList'" 或 "Possible unintended reference modification"
  3. 设计缺陷

    • 方法签名中直接传递了 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;
}

这种方式更加清晰,避免了对方法参数的直接修改。

总结

  1. 引用的重要性

    • 在 Java 中,集合是引用类型,操作时需要注意引用的变化。
    • 如果需要修改集合的内容,应直接操作原集合,而不是创建新的集合。
  2. IDEA 警告的价值

    • IDEA 的警告往往是潜在问题的提示,应尽量消除所有警告。
    • 如果警告无法消除,应明确其原因并添加注释说明。
  3. 方法设计原则

    • 避免在方法内部直接重新赋值传入的参数。
    • 如果需要返回新的结果,优先通过返回值或包装类传递。
  4. 类似问题的预防

    • 集合去重:在使用 Set 去重时,确保正确实现了 hashCode 和 equals 方法。
    • 引用传递:理解引用传递的机制,避免无意中创建新的对象。
    • 代码审查:在代码审查阶段重点关注方法参数的使用,避免不必要的副作用。
最后修改于:2025年04月14日 16:03

添加新评论