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

具体问题:

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,但这应该不是什么大问题,因为它可以很容易地以现在相反的顺序迭代或流。

其他回答

如何避免这样做:

不要使用.sorted(Comparator.reverseOrder())或.sorted(Collections.reverseOrder()),因为它只会按降序排序元素。 对于给定的Integer输入使用它: [1,4,2,5,3] 输出结果如下: [5,4,3,2,1] 对于字符串输入: [" a ", " d ", " b ", " e ", " c "] 输出结果如下: [e, d, c, b, a] 不要使用.sorted((a, b) -> -1)(后面的解释)

最简单的正确方法是:

List<Integer> list = Arrays.asList(1, 4, 2, 5, 3);
Collections.reverse(list);
System.out.println(list);

输出: [3,5,2,4,1]

String也一样:

List<String> stringList = Arrays.asList("A", "D", "B", "E", "C");
Collections.reverse(stringList);
System.out.println(stringList);

输出: [c, e, b, d, a]

不要使用.sorted((a, b) -> -1)! 它打破了比较国契约,可能只适用于某些情况。只在单线程上,而不是并行。 洋基的解释:

(a, b) -> -1打破了Comparator的契约。这是否有效取决于排序算法的实现。JVM的下一个版本可能会打破这一点。实际上,我已经可以在我的机器上使用IntStream重复地打破这个。.boxed范围(0 10000).parallel()()。sorted((a, b) -> -1).forEachOrdered(System.out::println);

//Don't use this!!!
List<Integer> list = Arrays.asList(1, 4, 2, 5, 3);
List<Integer> reversedList = list.stream()
        .sorted((a, b) -> -1)
        .collect(Collectors.toList());
System.out.println(reversedList);

正数输出: [3,5,2,4,1]

可能的输出在并行流或与其他JVM实现: [4,1,2,3,5]

String也一样:

//Don't use this!!!
List<String> stringList = Arrays.asList("A", "D", "B", "E", "C");
List<String> reversedStringList = stringList.stream()
        .sorted((a, b) -> -1)
        .collect(Collectors.toList());
System.out.println(reversedStringList);

正数输出: [c, e, b, d, a]

可能的输出在并行流或与其他JVM实现: [a, e, b, d, c]

以下是我想出的解决方案:

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

这个实用方法怎么样?

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

似乎对所有案件都有效,没有重复。

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

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在前向顺序有效地插入收集项(在列表的末尾),和番石榴列表。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);
    }
}

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

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

原来的答案(包含一个错误-它不能正确工作的并行流):

一个通用的流反向方法可以是这样的:

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