Java 8 流反向顺序

2022-08-31 06:08:04

一般问题:反转流的正确方法是什么?假设我们不知道流由什么类型的元素组成,那么反转任何流的通用方法是什么?

具体问题:

IntStream提供了范围方法在特定范围内生成整数,现在我想反转它,将范围从0切换到负值将不起作用,我也不能使用IntStream.range(-range, 0)Integer::compare

List<Integer> list = Arrays.asList(1,2,3,4);
list.stream().sorted(Integer::compare).forEach(System.out::println);

我会得到这个编译器错误IntStream

错误:(191, 0) ajc: 类型中的方法不适用于参数 (sorted()IntStreamInteger::compare)

我在这里错过了什么?


答案 1

对于生成反向的特定问题,请尝试如下操作:IntStream

static IntStream revRange(int from, int to) {
    return IntStream.range(from, to)
                    .map(i -> to - i + from - 1);
}

这避免了装箱和排序。

对于如何反转任何类型的流的一般问题,我不知道是否有“适当”的方法。我能想到几种方法。两者最终都存储流元素。我不知道在不存储元素的情况下反转流的方法。

第一种方法将元素存储到数组中,并以相反的顺序将它们读出到流中。请注意,由于我们不知道流元素的运行时类型,因此我们无法正确键入数组,这需要未经检查的强制转换。

@SuppressWarnings("unchecked")
static <T> Stream<T> reverse(Stream<T> input) {
    Object[] temp = input.toArray();
    return (Stream<T>) IntStream.range(0, temp.length)
                                .mapToObj(i -> temp[temp.length - i - 1]);
}

另一种技术使用收集器将项目累积到反向列表中。这会在对象的前面进行大量插入,因此会进行大量复制。ArrayList

Stream<T> input = ... ;
List<T> output =
    input.collect(ArrayList::new,
                  (list, e) -> list.add(0, e),
                  (list1, list2) -> list1.addAll(0, list2));

使用某种自定义数据结构编写一个更高效的反向收集器可能是可能的。

更新 2016-01-29

由于这个问题最近引起了一些关注,我想我应该更新我的答案,以解决在前面插入的问题。对于大量元素,这将是非常低效的,需要O(N^2)复制。ArrayList

最好使用 a 代替,这样可以有效地支持在前面插入。一个小皱纹是我们不能使用三 arg 形式的 ;它要求将第二个参数的内容合并到第一个参数中,并且在 上没有“在前面添加所有”批量操作。相反,我们使用将第一个参数的内容附加到第二个参数的末尾,然后返回第二个参数。这需要使用工厂方法。ArrayDequeStream.collect()DequeaddAll()Collector.of()

完整的代码是这样的:

Deque<String> output =
    input.collect(Collector.of(
        ArrayDeque::new,
        (deq, t) -> deq.addFirst(t),
        (d1, d2) -> { d2.addAll(d1); return d2; }));

结果是 a 而不是 a,但这应该不是什么大问题,因为它可以很容易地以现在相反的顺序迭代或流式传输。DequeList


答案 2

优雅的解决方案

List<Integer> list = Arrays.asList(1,2,3,4);
list.stream()
    .sorted(Collections.reverseOrder()) // Method on Stream<Integer>
    .forEach(System.out::println);

推荐