小编典典

Java 8 流逆序

all

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

具体问题:

IntStream提供范围方法来生成特定范围内的整数IntStream.range(-range, 0),现在我想反转它从 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()类型IntStream中的方法不适用于参数 ( Integer::compare)

我在这里想念什么?


阅读 94

收藏
2022-06-27

共1个答案

小编典典

对于生成 reverse 的具体问题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

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

最好使用 anArrayDeque代替,它可以有效地支持在前面插入。一个小问题是我们不能使用 ;
的三参数形式Stream.collect()。它要求将第二个 arg 的内容合并到第一个 arg 中,并且没有“add-all-at-
front”批量操作Deque。相反,我们使用addAll()将第一个 arg
的内容附加到第二个的末尾,然后返回第二个。这需要使用Collector.of()工厂方法。

完整的代码是这样的:

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

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

2022-06-27