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

Pra*_*ath 16 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"));
Run Code Online (Sandbox Code Playgroud)

Record是一个简单的POJO,它有id和name

我想在列表中做那些.

  • 创建一个像Map<Integer, List<Record>>这样的地图有一个密钥是id和更细的密钥添加为列表.我已经做了如下.

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

    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()))));
    
    Run Code Online (Sandbox Code Playgroud)

我已经尝试过,看起来这不是一个好方法.有谁能建议更好的方法?

Ole*_*hov 11

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

Map<Integer, List<Record>> map = list.stream()
    .collect(Collectors.groupingBy(
        Record::getId,
        Collectors.collectingAndThen(
            Collectors.toList(),
            records -> records.stream()
                              .sorted(Comparator.comparing(Record::getName))
                              .limit(limit)
                              .collect(Collectors.toList()))));
Run Code Online (Sandbox Code Playgroud)


Fed*_*ner 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()));
             })));
Run Code Online (Sandbox Code Playgroud)

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

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


Hol*_*ger 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();
});
Run Code Online (Sandbox Code Playgroud)

使用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;
                })));
Run Code Online (Sandbox Code Playgroud)

作为奖励,我还添加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)));
Run Code Online (Sandbox Code Playgroud)

  • 我不太清楚清除补充列表,更不用说修剪它.我认为这取决于内存使用VS垃圾收集周期.有时您可能希望尽可能少地使用内存,但有时您可能希望尽可能降低延迟.在后一种情况下,如果您确实不需要,可能需要仔细测量并避免GC运行.但我想这都是理论,这一切都取决于每个具体案例和使用场景. (3认同)
  • @ Federico Peralta Schaffner我已经在答案中明确表示这是一个权衡.我要保持结果更长的时间,你不想保留一个`subList`,它只是原始列表的一个包装器,它不仅包含一个更长的数组,而且还包含对所有收集元素的引用( `Record`实例),不再可见,但不能收集垃圾.这种行为可以被认为是内存泄漏.但是,修剪并不是那么重要.我添加了StreamEx变体,它具有不首先收集多余元素的优点. (2认同)

tob*_*s_k 1

您可以在收集地图中的项目之前进行排序。对于限制位,您可以使用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()))));
Run Code Online (Sandbox Code Playgroud)

limit = 2,这导致

{1=[Record(id=1, name=11), Record(id=1, name=12)], 
 2=[Record(id=2, name=22)], 
 3=[Record(id=3, name=32)], 
 4=[Record(id=4, name=42)], 
 5=[Record(id=5, name=51), Record(id=5, name=52)]}
Run Code Online (Sandbox Code Playgroud)