缓存Java 8流

3
假设我有一个列表,在其中执行了多个流操作。
  bobs = myList.stream()
        .filter(person -> person.getName().equals("Bob"))
        .collect(Collectors.toList())

...

并且

  tonies = myList.stream()
        .filter(person -> person.getName().equals("tony"))
        .collect(Collectors.toList())

我可以不只这样做吗:

Stream<Person> stream = myList.stream();

这意味着我可以做以下事情:
  bobs = stream.filter(person -> person.getName().equals("Bob"))
        .collect(Collectors.toList())
  tonies = stream.filter(person -> person.getName().equals("tony"))
        .collect(Collectors.toList())

4
你可能已经注意到,你不能这样做。但是你能解释一下为什么想要缓存该流吗? - davidxxx
3
你甚至不能在同一个流(stream)“leg”上调用.filter超过一次(或任何其他操作),因为Stream是一个“管道”,任何操作(无论是否是最终操作)都会向管道添加一个新的“leg”,“封闭”原始管道输出;然后你必须使用新管道leg的输出,以此类推。这也看起来像是XY问题。你应该真正解释你想要做什么,这将避免人们猜测并帮助你得到你实际问题的答案。 - M. Prokhorov
1
Map<Boolean,List<Person>> map = stream.filter(p -> p.getName().matches("Bob|tony")) .collect(Collectors.partitioningBy(p -> p.getName().equals("Bob"))); bobs = map.get(true); tonies = map.get(false); - Holger
@Holger,您提出的解决方案限制了原始问题的二进制输入。不过,在二进制环境中仍然是有利的。 - marsouf
@ marsouf,如果需要的话,使用groupingBy代替partitioningBy很容易。但只要是二元选择,partitioningBy具有性能优势。更重要的是要识别“我如何流两次?”作为一个xy问题,这将有助于通过正确的思维方式解决即使是完全不同的未来问题。 - Holger
4个回答

4

不可以。一个Stream只能使用一次。当你尝试重复使用时,它会抛出以下错误:

java.lang.IllegalStateException: stream has already been operated upon or closed
       at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)

根据 Java文档:

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

但是一个简洁的解决方案是使用Stream Suplier。它看起来像下面这样:
Supplier<Stream<Person>> streamSupplier = myList::stream;
bobs = streamSupplier.get().filter(person -> person.getName().equals("Bob"))
        .collect(Collectors.toList())
tonies = streamSupplier.get().filter(person -> person.getName().equals("tony"))
        .collect(Collectors.toList())

但是,每次get调用都会返回一个新的流。


1
streamSupplier.get()myList.stream() 没有优势。而且它更长。 - David Conrad
保持名称较小,代码会更短 st.get() vs myList.stream() :D 无论如何,我刚刚向 OP 分享了一个想法,他可以用它来实现他所寻找的内容。 - Aman Chhabra

1
在您的情况下,您可以生成动态流水线。假设管道中唯一的变量是您按其过滤的人的名称。
我们可以将其表示为Function<String, Stream<Person>>,如下所示:
final Function<String, Stream<Person>> pipelineGenerator = name -> persons.stream().filter(person -> Objects.equals(person.getName(), name));

final List<Person> bobs = pipelineGenerator.apply("bob").collect(Collectors.toList());

final List<Person> tonies = pipelineGenerator.apply("tony").collect(Collectors.toList());

1

不行,医生说:

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

但是你可以通过筛选所有你想要的元素一次,然后按需要分组来使用单个流:

Set<String> names = ...; // construct a sets containing bob, tony, etc
Map<String,List<Person>> r = myList.stream()
                                   .filter(p -> names.contains(p.getName())
                                   .collect(Collectors.groupingBy(Person::getName);
List<Person> tonies = r.get("tony");
List<Person> bobs = r.get("bob");

0

如前所述,给定的流应该仅被操作一次。

如果您需要多次引用一个对象,或者只是为了避免创建不必要的对象,我可以理解缓存对象的引用的“想法”。

然而,当您需要再次查询时,每次调用myList.stream()时,您不应该担心创建流,因为通常创建流是一项廉价的操作。


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