有啥方法可以重用 Stream 吗? [复制]

Posted

技术标签:

【中文标题】有啥方法可以重用 Stream 吗? [复制]【英文标题】:Is there any way to reuse a Stream? [duplicate]有什么方法可以重用 Stream 吗? [复制] 【发布时间】:2016-07-15 06:42:24 【问题描述】:

我正在学习 Java 8 的新功能,在尝试使用流 (java.util.stream.Stream) 和收集器时,我意识到一个流不能被使用两次。

有没有办法重复使用它?

【问题讨论】:

您可以在这里找到另一种选择:dzone.com/articles/how-to-replay-java-streams 【参考方案1】:

来自documentation:

一个流只能被操作(调用一个中间或终端流操作)一次。

如果检测到流正在被重用,流实现可能会抛出 IllegalStateException。

所以答案是否定的,流不应该被重用。

【讨论】:

shouldmay 这两个词对于推断明确的 answer is no 是不是有点弱? @Armali 你是对的,因为这些术语听起来并不具体。那是因为Stream 是一个接口,而我上面引用的是 intended 行为。但是,实施者可能会在实施时违反合同。 Stream 的用户应该期待我上面引用的行为。这就是为什么流应该只被操作一次。至于抛出异常,这取决于实现者。 @Armali:他们可能已经面向RFC2199:datatracker.ietf.org/doc/html/rfc2119:>应该这个词,或者形容词“推荐”,意味着可能存在特别的正当理由忽略特定项目的情况,但在选择不同的课程之前,必须了解并仔细权衡其全部含义。【参考方案2】:

如果您想获得重用流的效果,您可以将流表达式包装在 Supplier 中,并在需要新表达式时调用 myStreamSupplier.get()。例如:

Supplier<Stream<String>> sup = () -> someList.stream();
List<String> nonEmptyStrings = sup.get().filter(s -> !s.isEmpty()).collect(Collectors.toList());
Set<String> uniqueStrings = sup.get().collect(Collectors.toSet());

【讨论】:

这不会重用流。供应商只是创建一个新的流,以便在每次调用时使用。 这就是汉克 D 说“效果”的原因。它仍然是更清洁、更可重用的代码。 为什么要创建供应商来创建流,而不是在需要这样流的地方调用someList.stream() 同意这不是一个坏主意,如果有理由相信供应商可能需要其他一些提升,但我确实认为即使“效果”也有点误导。重用流的好处是不必再次创建它,对吗?不确定在使用Collection::stream() 创建(检索?)流时做了多少工作,但是当我用谷歌搜索它时,我想要的效果(可能没有必要)是最小化对象实例化。 来自Supplier java doc:不要求每次调用供应商时都返回新的或不同的结果。【参考方案3】:

正如其他人所说,“不,你不能”。

但记住方便的summaryStatistics() 对许多基本操作很有用:

所以而不是:

List<Person> personList = getPersons();

personList.stream().mapToInt(p -> p.getAge()).average().getAsDouble();
personList.stream().mapToInt(p -> p.getAge()).min().getAsInt();
personList.stream().mapToInt(p -> p.getAge()).max().getAsInt();

你可以:

// Can also be DoubleSummaryStatistics from mapToDouble()

IntSummaryStatistics stats = personList.stream()
                                       .mapToInt(p-> p.getAge())
                                       .summaryStatistics();

stats.getAverage();
stats.getMin();
stats.getMax();

【讨论】:

很好地补充了这个问题。我不知道这一点,事实证明它非常方便!【参考方案4】:

Stream 的整体理念是一次性使用。这允许您在没有中间存储的情况下创建不可重新输入的源(例如,从网络连接中读取行)。但是,如果您想重用 Stream 内容,可以将其转储到中间集合中以获取“硬拷贝”:

Stream<MyType> stream = // get the stream from somewhere
List<MyType> list = stream.collect(Collectors.toList()); // materialize the stream contents
list.stream().doSomething // create a new stream from the list
list.stream().doSomethingElse // create one more stream from the list

如果您不想具体化流,在某些情况下,有多种方法可以同时对同一流执行多项操作。例如,您可以参考this 或this 的问题了解详情。

【讨论】:

【参考方案5】:

正如其他人所指出的,流对象本身不能被重用。

但获得重用流效果的一种方法是将流创建代码提取到函数中

您可以通过创建包含流创建代码的方法或函数对象来做到这一点。然后你可以多次使用它。

例子:

public static void main(String[] args) 
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

    // The normal way to use a stream:
    List<String> result1 = list.stream()
        .filter(i -> i % 2 == 1)
        .map(i -> i * i)
        .limit(10)
        .map(i -> "i :" + i)
        .collect(toList());

    // The stream operation can be extracted to a local function to
    // be reused on multiple sources:
    Function<List<Integer>, List<String>> listOperation = l -> l.stream()
        .filter(i -> i % 2 == 1)
        .map(i -> i * i)
        .limit(10)
        .map(i -> "i :" + i)
        .collect(toList());

    List<String> result2 = listOperation.apply(list);
    List<String> result3 = listOperation.apply(Arrays.asList(1, 2, 3));

    // Or the stream operation can be extracted to a static method,
    // if it doesn't refer to any local variables:
    List<String> result4 = streamMethod(list);

    // The stream operation can also have Stream as argument and return value,
    // so that it can be used as a component of a longer stream pipeline:
    Function<Stream<Integer>, Stream<String>> streamOperation = s -> s
        .filter(i -> i % 2 == 1)
        .map(i -> i * i)
        .limit(10)
        .map(i -> "i :" + i);

    List<String> result5 = streamOperation.apply(list.stream().map(i -> i * 2))
        .filter(s -> s.length() < 7)
        .sorted()
        .collect(toCollection(LinkedList::new));


public static List<String> streamMethod(List<Integer> l) 
    return l.stream()
        .filter(i -> i % 2 == 1)
        .map(i -> i * i)
        .limit(10)
        .map(i -> "i :" + i)
        .collect(toList());


另一方面,如果您已经有一个要迭代多次的流对象,那么您必须将流的内容保存在某个集合对象中。

然后,您可以从比集合中获取具有相同内容的多个流。

例子:

public void test(Stream<Integer> stream) 
    // Create a copy of the stream elements
    List<Integer> streamCopy = stream.collect(toList());

    // Use the copy to get multiple streams
    List<Integer> result1 = streamCopy.stream() ...
    List<Integer> result2 = streamCopy.stream() ...

【讨论】:

【参考方案6】:

想一想,这种“重用”流的意愿只是通过良好的内联操作执行所需结果的意愿。所以,基本上,我们这里要说的是,在我们写完一个终端操作之后,我们可以做些什么来继续处理?

1) 如果你的终端操作返回一个collection,问题马上就解决了,因为每个collection都可以转回一个流(JDK 8)。

List<Integer> l=Arrays.asList(5,10,14);
        l.stream()
            .filter(nth-> nth>5)
            .collect(Collectors.toList())
            .stream()
            .filter(nth-> nth%2==0).forEach(nth-> System.out.println(nth));

2) 如果你的终端操作返回一个optional,通过JDK 9 对Optional 类的增强,你可以将Optional 结果变成一个流,并获得所需的nice inline 操作:

List<Integer> l=Arrays.asList(5,10,14);
        l.stream()
            .filter(nth-> nth>5)
            .findAny()
            .stream()
            .filter(nth-> nth%2==0).forEach(nth-> System.out.println(nth));

3) 如果您的终端操作返回其他内容,我真的怀疑您是否应该考虑使用流来处理此类结果:

List<Integer> l=Arrays.asList(5,10,14);
        boolean allEven=l.stream()
            .filter(nth-> nth>5)
            .allMatch(nth-> nth%2==0);
        if(allEven)
            ...
        

【讨论】:

【参考方案7】:

Functional Java library 提供自己的流来满足您的要求,即它们是记忆化的和惰性的。您可以使用它的转换方法在 Java SDK 对象和 FJ 对象之间进行转换,例如Java8.JavaStream_Stream(stream) 将返回一个给定 JDK 8 流的可重用 FJ 流。

【讨论】:

以上是关于有啥方法可以重用 Stream 吗? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

取消注册单元格以在 TableView/CollectionView 中重用有啥意义吗?

仅用于代码重用c ++的继承

c++:Status问题(status是c++中的关键字吗?是一种数据类型吗?它有啥作用?)

IPython Notebook:代码重用

有啥方法可以将 Node 项目转换为 Deno?

java stream 为啥不能重用