从lambda表达式中排序和子列表

时间:2018-02-16 13:44:44

标签: java lambda java-8

我有一个包含以下元素的数组列表:

List<Record> list = new ArrayList<>();
list.add(new Record(3, "32"));
list.add(new Record(4, "42"));
list.add(new Record(1, "12"));
list.add(new Record(1, "11"));
list.add(new Record(2, "22"));
list.add(new Record(5, "52"));
list.add(new Record(5, "53"));
list.add(new Record(5, "51"));

记录是一个简单的POJO,其ID和名称为

我想在列表中执行这些操作。

  • 创建一个像Map<Integer, List<Record>>这样的地图,其中一个密钥是id,并且更加简洁的密钥添加为列表。我已经完成了下面的操作。

    Map<Integer, List<Record>> map = list.stream()
        .collect(Collectors.groupingBy(Record::getId, HashMap::new, Collectors.toList()));
    
  • 现在我想按名称和子列表对列表进行排序,以提供地图内的限制

    map.forEach((k, v) -> v.stream().sorted(Comparator.comparing(Record::getName)));    
    map.forEach((k, v) -> map.put(k, v.subList(0, Math.min(**limit**, v.size()))));
    

我上面尝试过,看起来这不是一个好方法。任何人都可以提出更好的方法吗?

5 个答案:

答案 0 :(得分:11)

您可以使用Java 8 Collectors.collectingAndThen()方法:

@EnableWs

答案 1 :(得分:9)

您可以使用Collectors.collectingAndThen

Map<Integer, List<Record>> result = list.stream()
    .collect(Collectors.groupingBy(
         Record::getId,
         Collectors.collectingAndThen(
             Collectors.toCollection(ArrayList::new),
             v -> {
                 v.sort(Comparator.comparing(Record::getName));
                 return v.subList(0, Math.min(LIMIT, v.size()));
             })));

此解决方案避免为每个列表组创建新流。

正如在this answer中指出的那样,通过使用Collectors.toCollection(ArrayList::new),我们确保列表是可变的,以便我们以后可以对其进行排序。

答案 2 :(得分:5)

您可以使用

Map<Integer, List<Record>> map = list.stream()
    .collect(Collectors.groupingBy(Record::getId,Collectors.toCollection(ArrayList::new)));
map.values().forEach(l -> {
    list.sort(Comparator.comparing(Record::getName));
    l.subList(limit, l.size()).clear();
});

使用Collectors.toCollection(ArrayList::new)我们确保结果列表是可变的。然后我们就地对列表进行排序并删除不必要的值。我们不是构建包含我们想要的元素的子列表(它将保留对完整列表的引用),而是构建我们不想要的元素的子列表和clear()它,以便有效地从原始列表中删除这些元素

您也可以将其写为单一声明:

    Map<Integer, List<Record>> map = list.stream()
        .collect(Collectors.groupingBy(Record::getId,
            Collectors.collectingAndThen(
                Collectors.toCollection(ArrayList::new),
                l -> {
                    list.sort(Comparator.comparing(Record::getName));
                    l.subList(limit, l.size()).clear();
                    l.trimToSize();
                    return l;
                })));

作为奖励,我还添加了l.trimToSize();,如果前面的ArrayList删除了很多元素,它会指示.subList(limit, l.size()).clear()使用较小的数组。由于这可能意味着复制操作,因此这是CPU时间和内存之间的权衡。因此,如果结果仅在之后的相当短的时间内使用,则不会使用trimToSize()

当您使用StreamEx

时,操作变得更简单(并且可能更有效)
Map<Integer, List<Record>> map = list.stream()
    .collect(Collectors.groupingBy(Record::getId,
             MoreCollectors.least(Comparator.comparing(Record::getName), limit)));

答案 3 :(得分:4)

list.stream()
    .collect(Collectors.groupingBy(
          Record::getId,
          Collectors.collectingAndThen(
                   Collectors.toList(),
                   x -> x.stream()
                         .sorted(Comparator.comparing(Record::getName))
                         .limit(limit)
                         .collect(Collectors.toList())))); 

答案 4 :(得分:1)

您可以在收集地图中的项目之前对进行排序。对于限制位,您可以使用2-0.txt对列表进行后处理,并collectingAndThen

stream.limit

使用Map<Integer, List<Record>> map = list.stream() .sorted(Comparator.comparing(Record::getName)) .collect(Collectors.groupingBy(Record::getId, Collectors.collectingAndThen(Collectors.toList(), l -> l.stream().limit(limit).collect(Collectors.toList())))); ,结果为

limit = 2