【问题标题】:How to convert nested for loop into Hashmap using java stream如何使用java流将嵌套的for循环转换为Hashmap
【发布时间】:2018-05-01 19:43:53
【问题描述】:

我正在尝试使用 java 流将下面的嵌套 for 循环转换为 hashmap,但我在收集器步骤中被击中。你能帮忙吗?

现有代码:

private static HashMap<String, Long> getOutput(List<Employee> eList) {
    HashMap<String, Long> outputList = new HashMap<>();
    for (Employee employee : eList) {
           List<Department> departmentList = employee.getDepartmentList();
              for (Department department : departmentList) {
                 if (department.getType().equals(DepartmentType.SCIENCE)) {
                     outputList.put(employee.getName(),department.getDepartmentId()));
                  }
              }
    }
    return outputList;
}

到目前为止我尝试过:

private static HashMap<String, Long> getOutput(List<Employee> eList) {
                       return  eList.stream()
                                    .flatMap(emp -> emp.getDepartmentList().stream()
                                    .filter(dept -> dept.getType().equals(DepartmentType.SCIENCE))
                                    .collect(HashMap::new, ???)
              }

【问题讨论】:

  • 这可能会为您指明正确的方向stackoverflow.com/a/20887747/4252352
  • 您现有的代码不起作用。 outputListOutputList 的拼写不一致,而且它的类型 Map 与方法的返回类型 HashMap 不匹配。如果这是“现有代码”,是什么阻止了您从 IDE 中简单地复制工作代码而不是在浏览器中对其进行原型设计?
  • @Holger,感谢您指出错误。实际上我无法粘贴生产代码,所以我将 Employee 和 Department 原型作为示例。修复问题中的复制粘贴错误。

标签: java java-stream


【解决方案1】:

您的主要问题似乎是在完成flatMap 之后保留流的当前emp 引用。要保留此引用,您需要 flatMap 到某种可以同时包含 Employee 和 Department 的类 - 例如通用 Tuple(又名 Pair)。

Java 的 API 中没有内置直观的 Tuple 类,因此您的选择是:

  1. 使用提供 Tuple 类的第 3 方库(例如 javatuples
  2. DIY:构建您自己的通用 Tuple 类(参见相关的SO question
  3. 快速:添加专门为此 lambda 设计的私有内部类

编辑:

cmets(感谢@Holger!)启发了每个员工似乎有很多部门。我的原始代码有抛出异常的风险,因为会有重复的键,而 OP 的原始代码只是覆盖了映射条目。考虑使用groupingBycollector 并更改此方法的返回类型。

private static Map<String, List<Long>> getOutput(List<Employee> eList) {
  return eList.stream()
    // get a stream of employee / department pairs
    .flatMap(emp -> emp.getDepartmentList().stream().map(dep -> new EmployeeDepartmentPair(emp, dep))
    // filter the departments to SCIENCE
    .filter(x -> x.department.getType().equals(DepartmentType.SCIENCE))
    // group departmentIds by employee name
    .collect(Collectors.groupingBy(x -> x.employee.getName(), Collectors.mapping(x -> x.department.getDepartmentId(), Collectors.toList())))
}

原件(见上文编辑):

以下是使用选项 3 的一些更新代码:

private static Map<String, Long> getOutput(List<Employee> eList) {
  return  eList.stream()
    .flatMap(emp -> emp.getDepartmentList().stream().map(dep -> new EmployeeDepartmentPair(emp, dep))
    .filter(x -> x.department.getType().equals(DepartmentType.SCIENCE))
    .collect(Collectors.toMap(x -> x.employee.getName(), x -> x.department.getDepartmentId()));
}

private static class EmployeeDepartmentPair {
  public final Employee employee;
  public final Department department;

  public EmployeeDepartmentPair(Employee emp, Department d) {
    this.employee = emp;
    this.department = d;
  }
}

【讨论】:

  • @Holger 不错。我已经更新了返回类型。另一种选择是将结果映射设置为一个变量,然后从该映射创建一个新的 HashTable。虽然,强制此方法返回 HashMap 而不是 Map 并不是一个很好的理由。
  • 为什么不用Map.Entry 而不是EmployeeDepartmentPair
  • 我刚刚注意到,即使是 OP 的原始代码在地图类型方面也不一致。除此之外,还有一个逻辑问题。如果我们假设一个员工可能有多个部门(否则,为什么它是一个列表),那么结果地图中就会出现冲突。两名员工可能同名的事实使事情变得更糟。你的代码会在碰撞情况下抛出异常,但原来的静默覆盖行为也不是解决方案……
  • @rkosegi 你可以。 Map.Entry 被某些人视为非官方的Pair,但这是风格问题。我个人觉得,如果不是地图入口,我不应该使用它。
  • @Holger 好主意,我将更新答案以包括Collectors.groupingBy,因为看起来每个员工有很多部门。
【解决方案2】:

这永远不会像流一样漂亮,因为您需要第一个过滤器中的部门来计算要放入地图的值。因此,您需要对部门进行两次过滤:第二次查找第一次为您提供正匹配的部门并获取其 Id 值。

恕我直言,这段代码最好以其当前形式格式化,因为它可以更清楚地掌握它的确切功能,并且更易于调试。相比之下,同样的代码变成了流:

return eList.stream()
    .flatMap(emp -> emp.getDepartmentList().stream()
    .filter(dep -> dep.getType().equals(DepartmentType.SCIENCE))).collect(
    Collectors.toMap(Employee::getName, emp -> emp.getDepartmentList().stream()
    .filter(dep ->dep.getType.equals(DepartmentType.SCIENCE)
    .findFirst().get().getDepartmentId())), (s, a) -> a); 
}

基本上,您的问题中缺少的是Collectors.toMap()方法,它以参数为参数:

  1. 钥匙;
  2. 值(需要从键中计算出来);
  3. 合并功能,告诉收集器在重复项目的情况下该怎么做;在这种情况下,只保留第一个值。还有一个只有两个参数的toMap() 方法,但如果插入重复值,它会抛出一个IllegalStateException

【讨论】:

    【解决方案3】:

    我知道这有点晚了,但这里是对您已经很漂亮的答案组合的贡献+解释:

    private static HashMap<String, Long> getOutput(List<Employee> eList) {
            return eList
                    .stream() // for each Employee
                    .flatMap(emp -> emp.getDepartmentList() // get all his departments
                                        .stream()                   
                                        //filter departments by your predicate
                                        .filter(dept -> dept.getType().equals(DepartmentType.SCIENCE))
                                        // build an Entry with the employee and each department  
                                        .map(dept -> new SimpleEntry(emp.getName(),dept.getDepartmentId())))
                    // Each SimpleEntry<Name,DeptId> is then added to the your final Map
                    .collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue, (val1, val2) ->{ return val1;},HashMap::new));
    }
    

    SimpleEntry 只是Map.Entry 接口的一个实现:

    public class SimpleEntry implements Entry<String, Long> {
    
        private String name;
        private Long deptId;
    
        public SimpleEntry(String name, Long deptId) {
            this.name = name;
            this.deptId = deptId;
        }
        @Override
        public String getKey() {
            return this.name;
        }
        @Override
        public Long getValue() {
            return this.deptId;
        }
        @Override
        public Long setValue(Long value) {
            return this.deptId = value;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-05-14
      • 2021-06-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-05
      • 1970-01-01
      • 2020-05-19
      相关资源
      最近更新 更多