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

具体问题:

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

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

使用IntStream,我将得到这个编译器错误

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

我错过了什么?


当前回答

对于生成反向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]);
}

另一种技术使用收集器将项累积到反向列表中。它在数组列表对象的前面做了很多插入,所以有很多复制在进行。

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)个复制。

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

完整的代码如下:

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

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

其他回答

作为参考,我正在考虑同样的问题,我想以相反的顺序连接流元素的字符串值。

itemList = {last, middle, first} =>第一个,中间,最后一个

我开始从comonad或Stuart Marks的ArrayDeque收集器中使用colltingandthen的中间收集,尽管我对中间收集不满意,然后再次使用流式

itemList.stream()
        .map(TheObject::toString)
        .collect(Collectors.collectingAndThen(Collectors.toList(),
                                              strings -> {
                                                      Collections.reverse(strings);
                                                      return strings;
                                              }))
        .stream()
        .collect(Collector.joining());

所以我迭代了Stuart Marks的答案,它使用了收集器。factory,它有一个有趣的结束项。

itemList.stream()
        .collect(Collector.of(StringBuilder::new,
                             (sb, o) -> sb.insert(0, o),
                             (r1, r2) -> { r1.insert(0, r2); return r1; },
                             StringBuilder::toString));

由于在这种情况下,流不是并行的,组合器不是那么相关,我使用插入无论如何为了代码的一致性,但这并不重要,因为它将取决于哪个stringbuilder是第一个构建的。

我看了看StringJoiner,但是它没有插入方法。

ArrayDeque在堆栈中比stack或LinkedList更快。"push()"将元素插入到Deque的前面

 protected <T> Stream<T> reverse(Stream<T> stream) {
    ArrayDeque<T> stack = new ArrayDeque<>();
    stream.forEach(stack::push);
    return stack.stream();
}

最简单的解决方案是使用List::listIterator和Stream::generate

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
ListIterator<Integer> listIterator = list.listIterator(list.size());

Stream.generate(listIterator::previous)
      .limit(list.size())
      .forEach(System.out::println);

反转字符串或任何数组

(Stream.of("abcdefghijklm 1234567".split("")).collect(Collectors.collectingAndThen(Collectors.toList(),list -> {Collections.reverse(list);return list;}))).stream().forEach(System.out::println);

Split可以根据分隔符或空格进行修改

最简单的方法(simple collect -支持并行流):

public static <T> Stream<T> reverse(Stream<T> stream) {
    return stream
            .collect(Collector.of(
                    () -> new ArrayDeque<T>(),
                    ArrayDeque::addFirst,
                    (q1, q2) -> { q2.addAll(q1); return q2; })
            )
            .stream();
}

高级方式(以持续的方式支持并行流):

public static <T> Stream<T> reverse(Stream<T> stream) {
    Objects.requireNonNull(stream, "stream");

    class ReverseSpliterator implements Spliterator<T> {
        private Spliterator<T> spliterator;
        private final Deque<T> deque = new ArrayDeque<>();

        private ReverseSpliterator(Spliterator<T> spliterator) {
            this.spliterator = spliterator;
        }

        @Override
        @SuppressWarnings({"StatementWithEmptyBody"})
        public boolean tryAdvance(Consumer<? super T> action) {
            while(spliterator.tryAdvance(deque::addFirst));
            if(!deque.isEmpty()) {
                action.accept(deque.remove());
                return true;
            }
            return false;
        }

        @Override
        public Spliterator<T> trySplit() {
            // After traveling started the spliterator don't contain elements!
            Spliterator<T> prev = spliterator.trySplit();
            if(prev == null) {
                return null;
            }

            Spliterator<T> me = spliterator;
            spliterator = prev;
            return new ReverseSpliterator(me);
        }

        @Override
        public long estimateSize() {
            return spliterator.estimateSize();
        }

        @Override
        public int characteristics() {
            return spliterator.characteristics();
        }

        @Override
        public Comparator<? super T> getComparator() {
            Comparator<? super T> comparator = spliterator.getComparator();
            return (comparator != null) ? comparator.reversed() : null;
        }

        @Override
        public void forEachRemaining(Consumer<? super T> action) {
            // Ensure that tryAdvance is called at least once
            if(!deque.isEmpty() || tryAdvance(action)) {
                deque.forEach(action);
            }
        }
    }

    return StreamSupport.stream(new ReverseSpliterator(stream.spliterator()), stream.isParallel());
}

注意,您可以快速扩展到其他类型的流(IntStream,…)。

测试:

// Use parallel if you wish only
revert(Stream.of("One", "Two", "Three", "Four", "Five", "Six").parallel())
    .forEachOrdered(System.out::println);

结果:

Six
Five
Four
Three
Two
One

其他注意事项:最简单的方法是,当它与其他流操作一起使用时就不那么有用了(收集连接破坏了并行性)。前进的方式就没有这个问题,它也保持了流的初始特征,比如排序,所以,它是在反向操作之后用于其他流操作的方式。