【问题标题】:Create a map from two different lists based on common conditions using Java 8 Streams使用 Java 8 Streams 根据常见条件从两个不同的列表创建地图
【发布时间】:2018-05-22 18:40:58
【问题描述】:

我有两个这样的列表实例:

List<NameAndAge> nameAndAgeList = new ArrayList<>();
nameAndAgeList.add(new NameAndAge("John", "28"));
nameAndAgeList.add(new NameAndAge("Paul", "30"));
nameAndAgeList.add(new NameAndAge("Adam", "31"));

List<NameAndSalary> nameAndSalaryList = new ArrayList<>();
nameAndSalaryList.add(new NameAndSalary("John", 1000));
nameAndSalaryList.add(new NameAndSalary("Paul", 1100));
nameAndSalaryList.add(new NameAndSalary("Adam", 1200));

NameAndAge 在哪里

class NameAndAge {
    public String name;
    public String age;

    public NameAndAge(String name, String age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return name + ": " + age;
    }
}

NameAndSalary

private class NameAndSalary {
    private String name;
    private double salary;

    public NameAndSalary(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    @Override
    public String toString() {
        return name + ": " + salary;
    }
}

现在,我想创建一个映射,其中第一个列表中的键为 NameAndAge 对象,第二个列表中的值为 NameAndSalary,其中两个对象的名称相同。

所以,当我打印地图时,它应该是这样的:

{John: 28=John: 1000.0}
{Paul: 30=Paul: 1100.0}
{Adam: 31=Adam: 1200.0}

我已经尝试过这样做,但最终返回类型是“void”,所以我对 Streams 不熟悉,所以我一无所知。

nameAndAgeList
    .forEach(n ->
        nameAndSalaryList
            .stream()
            .filter(ns -> ns.name.equals(n.name))
            .collect(Collectors.toList()));

有人可以请教如何使用 Java Streams API 实现这一点吗?

【问题讨论】:

  • 表示中的等号不应该是逗号吗?
  • @Matt,因为它是地图的系统输出,所以它宁愿打印相等而不是逗号

标签: java java-8 java-stream


【解决方案1】:

首先,假设你要创建一个HashMap,你的键类(NameAndAge)必须覆盖equalshashCode()

其次,为了高效,我建议你先从第二个List创建一个Map&lt;String,NameAndSalary&gt;

Map<String,NameAndSalary> helper =
    nameAndSalaryList.stream()
                     .collect(Collectors.toMap(NameAndSalary::getName,
                                               Function.identity()));

最后,你可以创建你想要的Map

Map<NameAndAge,NameAndSalary> output = 
    nameAndAgeList.stream()
                  .collect(Collectors.toMap(Function.identity(),
                                            naa->helper.get(naa.getName())));

【讨论】:

  • 我正在写一个类似的答案。在不增加额外复杂性的情况下必须做出其他假设:(1) 名称是唯一的,(2) 给定名称应始终出现在两个列表的对象中。
  • @WilliamPrice 是的,如果名称不唯一,则必须向Collectors.toMap() 添加合并功能。至于第二个假设,你可以忽略它。如果名称仅出现在第一个列表中,则相应的 NameAndAge 将在输出 Map 中分配一个空值。如果名称仅出现在第二个列表中,则不会出现在输出地图上。
  • Collectors.toMap() will throw NPE 如果键 或值null,即使目标地图实现允许这样做。
  • @Ravi Function.identity() 是一个函数,它返回你传入的任何内容。当您使用Function.identity() 作为toMap 收集器的密钥生成器时,这意味着密钥将是Stream 的元素(即第二个Stream 管道中的NameAndAge 实例)。
  • 这是唯一接近到 1. 可维护性的解决方案(如果将这些行提取到诸如 mapWithKeysmapWithValues 之类的通用方法中,可能会更多)和 2. 高效,因为它是 O(n),并且不会为在第一个中找到的每个元素(即 O(n^2))搜索整个第二个列表。
【解决方案2】:

这也应该可以解决问题:

Map<NameAndAge, NameAndSalary> map = new HashMap<>();
nameAndAgeList.forEach(age -> {
     NameAndSalary salary = nameAndSalaryList.stream().filter(
              s -> age.getName().equals(s.getName())).
              findFirst().
              orElseThrow(IllegalStateException::new);
      map.put(age, salary);
});

请注意,如果找不到匹配的名称,它会抛出 IllegalStateException

【讨论】:

  • 这正是我想要的。谢谢@Matt
【解决方案3】:

这应该也可以

List<String> commonNames = nameAndAgeList
    .stream()
    .filter(na -> 
         nameAndSalaryList.anyMatch((ns) -> ns.getName().equals(na.getName()))
    .collect(Collectors.toList());

Map<NameAndAge, NameAndSalary> map = 
    commonNames.stream().collect(Collectors.toMap(name -> 
        nameAndAgeList.get(name), name -> nameAndSalaryList.get(name)));

【讨论】:

    【解决方案4】:

    不创建任何中间对象,不使用forEach,一个衬垫:

    Map<NameAndAge, NameAndSalary> resultMap1 = nameAndAgeList.stream()
                .map(nameAndAge -> nameAndSalaryList.stream()
                        .filter(nameAndSalary -> nameAndAge.getName().equals(nameAndSalary.getName()))
                        .map(nameAndSalary -> new SimpleEntry<>(nameAndAge, nameAndSalary))
                        .collect(Collectors.toList()).get(0))
                .collect(Collectors.toMap(simpleEntry -> simpleEntry.getKey(), simpleEntry -> simpleEntry.getValue()));
    

    您必须将getter 函数添加到域类中。直接访问属性可能不是一个好主意。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多