一般问题:反转流的正确方法是什么?假设我们不知道流包含哪种类型的元素,那么反转任何流的通用方法是什么?
具体问题:
IntStream
提供范围方法来生成特定范围内的整数IntStream.range(-range, 0)
,现在我要反转它切换范围从0到负不起作用,我也不能使用Integer::compare
Listlist = Arrays.asList(1,2,3,4); list.stream().sorted(Integer::compare).forEach(System.out::println);
与IntStream
我会得到这个编译器错误
错误:(191,0)ajc:
sorted()
类型中的方法IntStream
不适用于参数(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]); }
另一种技术使用收集器将项目累积到反向列表中.这会在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
替代,它有效地支持前面的插入.一个小皱纹是我们不能使用三个arg形式Stream.collect()
; 它要求将第二个arg的内容合并到第一个arg中,并且没有"add-all-at-front"批量操作Deque
.相反,我们使用addAll()
将第一个arg的内容追加到第二个arg的末尾,然后我们返回第二个arg.这需要使用Collector.of()
工厂方法.
完整的代码是这样的:
Deque<String> output = input.collect(Collector.of( ArrayDeque::new, (deq, t) -> deq.addFirst(t), (d1, d2) -> { d2.addAll(d1); return d2; }));
结果是a Deque
而不是a List
,但这不应该是一个很大的问题,因为它可以很容易地以现在颠倒的顺序进行迭代或流式传输.
这里的许多解决方案排序或反转IntStream
,但这不必要地需要中间存储.Stuart Marks的解决方案是要走的路:
static IntStream revRange(int from, int to) { return IntStream.range(from, to).map(i -> to - i + from - 1); }
它正确处理溢出,通过此测试:
@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}); }
优雅的解决方
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; }); } }
一般问题:
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
方法不正确,因为它排序,而不是反转(假设流可能有无序元素)
具体问题:
我发现这个简单,容易和直观(复制@Holger评论)
IntStream.iterate(to - 1, i -> i - 1).limit(to - from)
如果实施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);
您可以定义自己的收集器,以相反的顺序收集元素:
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有效地提供列表的反向视图,而无需另外复制它.
以下是自定义收集器的一些测试用例:
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); } }
我建议使用jOO?,它是一个很棒的库,为Java 8流和lambda添加了许多有用的功能。
然后,您可以执行以下操作:
List<Integer> list = Arrays.asList(1,2,3,4);
Seq.seq(list).reverse().forEach(System.out::println)
就那么简单。这是一个非常轻量级的库,非常值得添加到任何Java 8项目中。
这是我提出的解决方案:
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...
cyclops -react StreamUtils有一个反向流方法(javadoc).
StreamUtils.reverse(Stream.of("1", "2", "20", "3")) .forEach(System.out::println);
它的工作原理是收集到一个ArrayList,然后使用可以向任一方向迭代的ListIterator类,在列表上向后迭代.
如果您已经有一个List,它将更有效率
StreamUtils.reversedStream(Arrays.asList("1", "2", "20", "3")) .forEach(System.out::println);