如何使用 java 8 将一组对象分组到排序列表中?

How to group a set of objects into sorted lists using java 8?

我想获取一组对象(在本例中为 ObjectInstance),我想将它们按一个分组 属性,然后将生成的列表按另一个排序。

Set<ObjectInstance> beans = server.queryMBeans(null, null);
Map<String, List<String>> beansByDomain = beans.stream()
            .collect(groupingBy( (ObjectInstance oi) -> oi.getObjectName().getDomain(),
                                mapping((ObjectInstance oi) -> oi.getObjectName().getCanonicalKeyPropertyListString(),
                                toList() )));

以上表达式创建了正确的数据结构:Map,其中键是 ObjectInstance 对象的域,值是 属性 列表的列表。我现在想要的是对列表进行排序,以确保它们按字母顺序排列。有什么方法可以在同一个表达式中做到这一点吗?

一个想法是在 .stream() 之后添加 .sort(),但这真的能保证有效吗?

使用collectingAndThen:

List<String> beansByDomain = beans.stream()
        .collect(groupingBy( (ObjectInstance oi) -> oi.getObjectName().getDomain(),
                            mapping((ObjectInstance oi) -> oi.getObjectName().getCanonicalKeyPropertyListString(),
                            collectingAndThen(toList(), (l -> l.stream().sorted().collect(toList()))) )));

您可以提取收集器以使代码更具可读性:

public static <T> Collector<T,?,List<T>> toSortedList() {
    return Collectors.collectingAndThen(Collectors.toList(), 
                                       l -> l.stream().sorted().collect(toList()));
}

 List<String> beansByDomain = beans.stream()
        .collect(groupingBy( (ObjectInstance oi) -> oi.getObjectName().getDomain(),
                            mapping((ObjectInstance oi) -> oi.getObjectName().getCanonicalKeyPropertyListString(),
                                    toSortedList())));

当然你可以在收集之前对整个流进行排序:

Map<String, List<String>> beansByDomain = beans.stream()
        .map(ObjectInstance::getObjectName)
        .sorted(Comparator.comparing(ObjectName::getCanonicalKeyPropertyListString))
        .collect(groupingBy(ObjectName::getDomain,
                            mapping(ObjectName::getCanonicalKeyPropertyListString,
                            toList() )));

请注意,我添加了 .map(ObjectInstance::getObjectName) 步骤,因为您不需要 ObjectInstance 中的任何其他内容。这会很好地工作,尽管我无法预测它是否比单独排序每个结果列表更快。

如果您更喜欢单独的 toSortingList() 收集器(如@JeanLogeart 的回答),可以这样优化:

public static <T extends Comparable<T>> Collector<T,?,List<T>> toSortedList() {
    return collectingAndThen(toCollection(ArrayList::new),
                    (List<T> l) -> {l.sort(Comparator.naturalOrder()); return l;});
}

这里我们显式地收集到 ArrayListtoList() 做同样的事情,但不能保证),然后在没有额外复制的情况下就地对结果列表进行排序(使用 stream().sorted().collect(toList()) 你至少复制整个列表内容两次)。另请注意,<T> 参数必须声明为 extends Comparable<T>。否则,您可能会错误地将此收集器用于不可比较的类型,该类型可以正常编译,但会导致运行时错误。