Java 8stream逆序

一般问题:什么是扭转stream的正确方法? 假设我们不知道stream包含的元素是什么types,那么什么是反转任何stream的通用方法?

具体问题:

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我会得到这个编译器错误

错误:( IntStream )ajc:typesIntStream中的sorted()方法不适用于参数( Integer::compare

我在那里错过了什么?

这里的许多解决scheme对IntStreamsorting或反转,但是这不必要地需要中间存储。 斯图尔特·马克斯的解决scheme是要走的路:

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

它也正确处理溢出,通过这个testing:

 @Test public void testRevRange() { assertArrayEquals(revRange(0, 5).toArray(), new int[]{4, 3, 2, 1, 0}); assertArrayEquals(revRange(-5, 0).toArray(), new int[]{-1, -2, -3, -4, -5}); assertArrayEquals(revRange(1, 4).toArray(), new int[]{3, 2, 1}); assertArrayEquals(revRange(0, 0).toArray(), new int[0]); assertArrayEquals(revRange(0, -1).toArray(), new int[0]); assertArrayEquals(revRange(MIN_VALUE, MIN_VALUE).toArray(), new int[0]); assertArrayEquals(revRange(MAX_VALUE, MAX_VALUE).toArray(), new int[0]); assertArrayEquals(revRange(MIN_VALUE, MIN_VALUE + 1).toArray(), new int[]{MIN_VALUE}); assertArrayEquals(revRange(MAX_VALUE - 1, MAX_VALUE).toArray(), new int[]{MAX_VALUE - 1}); } 

对于生成反向IntStream的具体问题,请尝试如下所示:

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

这避免了拳击和sorting。

对于如何扭转任何types的stream的一般问题,我不知道有一个“适当”的方式。 有一些我能想到的方法。 两者都最终存储stream元素。 我不知道如何在不存储元素的情况下翻转stream。

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

 @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对象的前面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)复制。

相反,最好使用ArrayDeque ,它有效地支持前面的插入。 一个小小的错误就是我们不能使用Stream.collect()的三个参数。 它要求将第二个参数的内容合并到第一个参数中,并且在Deque上没有“加载全部在前”的批量操作。 相反,我们使用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 ,但这不应该成为一个问题,因为它可以很容易地以现在相反的顺序迭代或stream式传输。

一般问题:

stream不存储任何元素。

因此,如果不将元素存储在某个中间集合中,则以相反顺序迭代元素是不可能的。

 Stream.of("1", "2", "20", "3") .collect(Collectors.toCollection(ArrayDeque::new)) // or LinkedList .descendingIterator() .forEachRemaining(System.out::println); 

更新:更改LinkedList ArrayDeque(更好) 看到这里的细节

打印:

 3 20 2 1 

顺便说一下,使用sort方法是不正确的,因为它sorting,不反转(假设stream可能有无序的元素)

具体问题:

我发现这个简单,容易和直观(Copied @Holger评论)

 IntStream.iterate(to - 1, i -> i - 1).limit(to - from) 

优雅的解决

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

没有外部的lib …

 import java.util.List; import java.util.Collections; import java.util.stream.Collector; public class MyCollectors { public static <T> Collector<T, ?, List<T>> toListReversed() { return Collectors.collectingAndThen(Collectors.toList(), l -> { Collections.reverse(l); return l; }); } } 

你可以定义你自己的收集器,以相反的顺序收集元素:

 public static <T> Collector<T, List<T>, List<T>> inReverse() { return Collector.of( ArrayList::new, (l, t) -> l.add(t), (l, r) -> {l.addAll(r); return l;}, Lists::<T>reverse); } 

并使用它:

 stream.collect(inReverse()).forEach(t -> ...) 

我按照顺序使用ArrayList来高效地插入收集项目(在列表的末尾)和Guava Lists.reverse以有效地给出列表的反转视图,而不用制作另一个副本。

以下是自定义收集器的一些testing用例:

 import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; import java.util.ArrayList; import java.util.List; import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collector; import org.hamcrest.Matchers; import org.junit.Test; import com.google.common.collect.Lists; public class TestReverseCollector { private final Object t1 = new Object(); private final Object t2 = new Object(); private final Object t3 = new Object(); private final Object t4 = new Object(); private final Collector<Object, List<Object>, List<Object>> inReverse = inReverse(); private final Supplier<List<Object>> supplier = inReverse.supplier(); private final BiConsumer<List<Object>, Object> accumulator = inReverse.accumulator(); private final Function<List<Object>, List<Object>> finisher = inReverse.finisher(); private final BinaryOperator<List<Object>> combiner = inReverse.combiner(); @Test public void associative() { final List<Object> a1 = supplier.get(); accumulator.accept(a1, t1); accumulator.accept(a1, t2); final List<Object> r1 = finisher.apply(a1); final List<Object> a2 = supplier.get(); accumulator.accept(a2, t1); final List<Object> a3 = supplier.get(); accumulator.accept(a3, t2); final List<Object> r2 = finisher.apply(combiner.apply(a2, a3)); assertThat(r1, Matchers.equalTo(r2)); } @Test public void identity() { final List<Object> a1 = supplier.get(); accumulator.accept(a1, t1); accumulator.accept(a1, t2); final List<Object> r1 = finisher.apply(a1); final List<Object> a2 = supplier.get(); accumulator.accept(a2, t1); accumulator.accept(a2, t2); final List<Object> r2 = finisher.apply(combiner.apply(a2, supplier.get())); assertThat(r1, equalTo(r2)); } @Test public void reversing() throws Exception { final List<Object> a2 = supplier.get(); accumulator.accept(a2, t1); accumulator.accept(a2, t2); final List<Object> a3 = supplier.get(); accumulator.accept(a3, t3); accumulator.accept(a3, t4); final List<Object> r2 = finisher.apply(combiner.apply(a2, a3)); assertThat(r2, contains(t4, t3, t2, t1)); } public static <T> Collector<T, List<T>, List<T>> inReverse() { return Collector.of( ArrayList::new, (l, t) -> l.add(t), (l, r) -> {l.addAll(r); return l;}, Lists::<T>reverse); } } 

独眼巨人反应 StreamUtils有一个逆stream方法( javadoc )。

  StreamUtils.reverse(Stream.of("1", "2", "20", "3")) .forEach(System.out::println); 

它的工作原理是收集一个ArrayList,然后利用可以在任一方向迭代的ListIterator类,在列表上向后迭代。

如果你已经有一个列表,它会更有效率

  StreamUtils.reversedStream(Arrays.asList("1", "2", "20", "3")) .forEach(System.out::println); 

如果实现了Comparable <T>(例如Integer,String,Date),可以使用Comparator.reverseOrder()来完成

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

以下是我提出的解决scheme:

 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... 

我build议使用jOOλ ,这是一个很棒的库,它为Java 8stream和lambdas添加了很多有用的function。

然后您可以执行以下操作:

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

就那么简单。 这是一个非常轻量级的库,值得添加到任何Java 8项目。

人们可以写一个收集器,以相反的顺序收集元素:

 public static <T> Collector<T, ?, Stream<T>> reversed() { return Collectors.collectingAndThen(Collectors.toList(), list -> { Collections.reverse(list); return list.stream(); }); } 

像这样使用它:

 Stream.of(1, 2, 3, 4, 5).collect(reversed()).forEach(System.out::println); 

原始答案 (包含一个错误 – 并行stream不能正常工作):

通用stream反向方法可能如下所示:

 public static <T> Stream<T> reverse(Stream<T> stream) { LinkedList<T> stack = new LinkedList<>(); stream.forEach(stack::push); return stack.stream(); } 

Java 8的方式来做到这一点:

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

作为参考,我正在看同样的问题,我想以相反的顺序joinstream元素的string值。

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

我开始使用收集中间集合,然后从comonadArrayDequecollections家,虽然我不满意中间收集,并再次stream

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

所以我重复了斯图尔特·马克斯(Stuart Marks)的回答,那就是使用Collector.of工厂( Collector.of factory),它有一个有趣的修整器 lambda。

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

因为在这种情况下stream不是并行的,组合器不是那么重要,为了代码的一致性,我使用insert ,但是没关系,因为它取决于哪个stringbuilder是先构build的。

我看了一下StringJoiner,但没有insert方法。

用IntStream回答具体的反转问题,下面为我工作:

 IntStream.range(0, 10) .map(x -> x * -1) .sorted() .map(Math::abs) .forEach(System.out::println); 

最简单的方法 (简单收集 – 支持并行stream):

 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(); } 

高级方式 (持续支持并行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()); } 

请注意,您可以快速扩展到其他types的stream(IntStream,…)。

testing:

 // 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 

其他注意事项: simplest way是与其他stream操作一起使用时不那么有用(collect连接打破了并行性)。 advance way没有这个问题,它也保持了stream的初始特性,例如SORTED ,所以,这是反向后与其他stream操作一起使用的方式。

反转列表的最通用和最简单的方法是:

 public static <T> void reverseHelper(List<T> li){ li.stream() .sorted((x,y)-> -1) .collect(Collectors.toList()) .forEach(System.out::println); }