如何使用未知数量的比较器对列表/流进行排序?

时间:2017-08-03 15:05:28

标签: java sorting java-8 java-stream

我有以下代码段:

List<O> os = new ArrayList<>();
os.add(new O("A", 3, "x"));
os.add(new O("A", 2, "y"));
os.add(new O("B", 1, "z"));

Comparator<O> byA = Comparator.comparing(O::getA);
Comparator<O> byB = Comparator.comparing(O::getB);

// I want to use rather this...    
List<Comparator<O>> comparators = new ArrayList<>();
comparators.add(byA);
comparators.add(byB);

os.stream()
    .sorted(byB.thenComparing(byA))
    .forEach(o -> System.out.println(o.getC()));

如您所见,我使用显式两个比较器进行排序。但是如果我在某个列表中有未知数量的比较器并且我想按它们排序呢?有什么办法吗?或者应该使用具有多个ifs的旧时尚方式比较器?

3 个答案:

答案 0 :(得分:15)

如果列表或任何其他集合中有多个比较器,您可以通过执行Stream上的缩减来替换它们:

List<Comparator<String>> comparators = ...

Comparator<String> combined = comparators.stream()
  .reduce(Comparator::thenComparing)
  .orElse(someDefaultComparator); // empty list case

所有实例将根据输入列表中的顺序使用thenComparing组合在一起。

使用简单的for循环使用非流方法可以实现相同的目的:

Comparator<String> result = comparators.get(0);
for (int i = 1; i < comparators.size(); i++) {
    result = result.thenComparing(comparators.get(i));
}

答案 1 :(得分:6)

通过在迭代中对累积的比较器和当前比较器调用reduce,可以.thenComparing()比较器流到单个比较器:

Optional<Comparator<O>> comparator = Optional.ofNullable(comparators.stream()
    .reduce(null, (acc, current) -> acc == null ? current : acc.thenComparing(current), (a, b) -> a));

os.stream()
    .sorted(comparator.orElse((a,b) -> 0))
    .forEach(o -> System.out.println(o.getC()));

在这个例子中,我使用Optional<Comparator<O>>并用Optional.ofNullable()包装缩小结果来处理具有空比较器列表的情况。然后,您可以决定何时将结果传递给sorted()方法,以便在列表为空的情况下执行操作 - 您可以使用不对任何内容进行排序的(a,b)->0比较器。

然后,您想要应用多少比较器并不重要。但是有一个 - 给定集合中的比较器的顺序。在给定的示例中,我按升序应用比较器(从列表的第一个元素开始到最后一个元素)。它会严重影响排序结果。

例如,在您的示例中,您调用了byB.thenComparing(byA)。它产生的结果与byA.thenComparing(byB)不同。我可以假设在某些情况下你想控制比较器的应用顺序。

现场演示:

  

https://jdoodle.com/a/4Xz

答案 2 :(得分:0)

如果您被允许使用谷歌番石榴,您可以简单地复合您的可比对象。

https://google.github.io/guava/releases/snapshot/api/docs/com/google/common/collect/Ordering.html#compound-java.lang.Iterable-

比较器比较器= Ordering.compound(比较器);

如果没有给出比较器,将保留原始订单。

关于此方法的番石榴的Javadoc虽然建议使用Java 8:

list.stream().sorted(comparators.stream().reduce(defaultComparator, Comparator::thenComparing));

因此,对于Java 7(或更低版本),建议使用'compound'解决方案。