有啥方法可以重用 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。
所以答案是否定的,流不应该被重用。
【讨论】:
should 和 may 这两个词对于推断明确的 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 中重用有啥意义吗?