Java 8流反转顺序

231

一般问题:如何正确地反转一个流?假设我们不知道这个流包含什么类型的元素,有什么通用方法可以反转任何流?

具体问题:

IntStream提供了range方法来在特定范围内生成整数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 我会得到以下编译错误:

Error:(191, 0) ajc: The method sorted() in the type IntStream is not applicable for the arguments (Integer::compare)

我错过了什么?


2
一个 IntStream 没有 .sorted(Comparator) 方法;你必须先通过一个 Stream<Integer> 并在那里反转后再产生一个 IntStream - fge
5
要生成倒序的 IntStream.range(0, n),可以使用类似于 map(i -> n - i - 1) 的方式。无需进行装箱和排序。 - Stuart Marks
3
您的一般问题和具体问题看起来像是两个完全不同的问题。一般问题谈论的是反转“流”,而具体问题则谈论将数字按降序排序。如果该“流”以无序方式生成数字,例如 1, 3, 2,您期望的结果是什么?您想要倒转后的“流”,例如 2, 3, 1,还是排序后的“流”,例如 3, 2, 1 - chiccodoro
10
通常情况下,你无法反转一个流 - 例如,流可能是无限的。 - assylias
1
您可能想要重新提出问题,作为“以Java 8的方式反向迭代集合”。答案可能超出流。下面来自@venkata-raju的答案解决了这个问题,但需要额外的空间。我仍在等待看到这个问题的好答案。 - Manu Manjunath
显示剩余5条评论
31个回答

7
以下是我想到的解决方案:
private static final Comparator<Integer> BY_ASCENDING_ORDER = Integer::compare;
private static final Comparator<Integer> BY_DESCENDING_ORDER = BY_ASCENDING_ORDER.reversed();

然后使用这些比较器:

IntStream.range(-range, 0).boxed().sorted(BY_DESCENDING_ORDER).forEach(// etc...

2
这只是对你的“具体问题”的回答,而不是对你的“一般问题”的回答。 - chiccodoro
14
Java 1.2 版本就有了 Collections.reverseOrder() 方法,可以用于对 Integer 类型进行排序。 - Holger

5
这个工具方法怎么样?
public static <T> Stream<T> getReverseStream(List<T> list) {
    final ListIterator<T> listIt = list.listIterator(list.size());
    final Iterator<T> reverseIterator = new Iterator<T>() {
        @Override
        public boolean hasNext() {
            return listIt.hasPrevious();
        }

        @Override
        public T next() {
            return listIt.previous();
        }
    };
    return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
            reverseIterator,
            Spliterator.ORDERED | Spliterator.IMMUTABLE), false);
}

似乎可以适用于所有情况而不重复。


1
我非常喜欢这个解决方案。大多数答案分为两类:(1) 反转集合并使用 .stream(),(2) 应用自定义收集器。这两种方法都是完全不必要的。否则,这将证明 JDK 8 本身存在一些严重的语言表达问题。而你的答案证明了相反的结论 :) - vitrums
请参见Adrian 2019年5月的答案。 - greybeard

4

关于生成反向IntStream的具体问题:

Java 9开始,您可以使用IntStream.iterate(...)的三个参数版本:

IntStream.iterate(10, x -> x >= 0, x -> x - 1).forEach(System.out::println);

// Out: 10 9 8 7 6 5 4 3 2 1 0

说明:

IntStream.iterate​(int seed, IntPredicate hasNext, IntUnaryOperator next);

  • seed - 初始元素;
  • hasNext - 应用于元素的谓词,以确定流何时终止;
  • next - 应用于前一个元素以生成新元素的函数。

3

虽然不是纯Java8,但如果你使用guava的Lists.reverse()方法,就可以轻松实现这一点:

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

3

最简单的方法(简单收集 - 支持并行流):

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

附加说明:使用简单方式与其他流操作一起使用时并不是很有用(collect join破坏了并行性)。而先进的方式没有这个问题,它还保持了流的初始特征,例如SORTED,因此,在反转后与其他流操作一起使用是必须采用的方式。


3

ArrayDeque 在堆栈操作中比 StackLinkedList 更快。"push()" 将元素插入 Deque 的前面。

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

3
List newStream = list.stream().sorted(Collections.reverseOrder()).collect(Collectors.toList());
        newStream.forEach(System.out::println);

2

参考一下,我正在研究相同的问题,我想要将流元素的字符串值按相反顺序连接。

itemList = { last, middle, first } => first,middle,last

我开始使用一个中间集合,其中包括comonadcollectingAndThenStuart MarksArrayDeque收集器,尽管我对中间集合不满意,并且再次进行流处理。

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

我参考了Stuart Marks的答案,使用了Collector.of工厂,其中有一个有趣的finisher lambda函数。

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

由于在这种情况下流不是并行的,因此组合器并不是很重要,为了代码一致性我仍然使用insert,但它并不重要,因为这将取决于先构建哪个stringbuilder。

我查看了StringJoiner,但它没有insert方法。


2
这种方法适用于任何流,并且符合Java 8标准:
Stream<Integer> myStream = Stream.of(1, 2, 3, 4, 5);
myStream.reduce(Stream.empty(),
        (Stream<Integer> a, Integer b) -> Stream.concat(Stream.of(b), a),
        (a, b) -> Stream.concat(b, a))
        .forEach(System.out::println);

2
最初的回答:翻转字符串或任何数组
反转字符串或任何数组
(Stream.of("abcdefghijklm 1234567".split("")).collect(Collectors.collectingAndThen(Collectors.toList(),list -> {Collections.reverse(list);return list;}))).stream().forEach(System.out::println);

"最初的回答" 可以根据分隔符或空格进行修改。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接