Lan*_*ani 12 java limit java-8 java-stream
我想知道我是否可以根据流之外的某种条件设置向流添加操作.例如,如果我的limit变量不等于,我想向流添加限制操作-1.
我的代码目前看起来像这样,但我还没有看到以这种方式使用流的其他示例,其中Stream对象被重新分配给应用于自身的中间操作的结果:
// Do some stream stuff
stream = stream.filter(e -> e.getTimestamp() < max);
// Limit the stream
if (limit != -1) {
stream = stream.limit(limit);
}
// Collect stream to list
stream.collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)
如此stackoverflow帖子中所述,在调用终端操作之前,实际上不会应用过滤器.由于我在调用终端操作之前重新分配了流的值,上面的代码仍然是使用Java 8流的正确方法吗?
Hol*_*ger 14
链接的一系列调用与一系列存储中间返回值的调用之间没有语义差异.因此,以下代码片段是等效的:
a = object.foo();
b = a.bar();
c = b.baz();
Run Code Online (Sandbox Code Playgroud)
和
c = object.foo().bar().baz();
Run Code Online (Sandbox Code Playgroud)
在任何一种情况下,都会在前一次调用的结果上调用每个方法.但在后一种情况下,中间结果不会被存储,但会在下一次调用时丢失.对于流API,在调用下一个方法之后不能使用中间结果,因此链接是使用流的自然方式,因为它本质上确保您不会在一个方法上调用多个方法.返回参考.
尽管如此,只要您遵守不多次使用返回引用的合同,将引用存储到流中并没有错.通过在问题中使用它,即使用下一次调用的结果覆盖变量,您还可以确保不在返回的引用上调用多个方法,因此,它是正确的用法.当然,这仅适用于相同类型的中间结果,因此当您使用map或flatMap获取不同引用类型的流时,您无法覆盖本地变量.然后你必须小心不再使用旧的局部变量,但是,如上所述,只要你在下次调用后没有使用它,中间存储就没有问题.
有时,你必须存储它,例如
try(Stream<String> stream = Files.lines(Paths.get("myFile.txt"))) {
stream.filter(s -> !s.isEmpty()).forEach(System.out::println);
}
Run Code Online (Sandbox Code Playgroud)
请注意,该代码等同于以下替代方案:
try(Stream<String> stream = Files.lines(Paths.get("myFile.txt")).filter(s->!s.isEmpty())) {
stream.forEach(System.out::println);
}
Run Code Online (Sandbox Code Playgroud)
和
try(Stream<String> srcStream = Files.lines(Paths.get("myFile.txt"))) {
Stream<String> tmp = srcStream.filter(s -> !s.isEmpty());
// must not be use variable srcStream here:
tmp.forEach(System.out::println);
}
Run Code Online (Sandbox Code Playgroud)
它们是等价的,因为forEach总是调用结果总是在结果上filter调用,Files.lines并且调整最终close()操作的结果无关紧要,因为关闭会影响整个流管道.
用一句话来说,你使用它的方式是正确的.
我甚至更喜欢这样做,因为limit当你不想应用限制时不链接操作是表达你意图的最干净的方式.值得注意的是,建议的替代方案可能在很多情况下都有效,但它们在语义上并不相同:
.limit(condition? aLimit: Long.MAX_VALUE)
Run Code Online (Sandbox Code Playgroud)
假设您可以遇到的元素的最大数量是,Long.MAX_VALUE但是流可以包含更多元素,它们甚至可能是无限的.
.limit(condition? aLimit: list.size())
Run Code Online (Sandbox Code Playgroud)
当流源是list,正在打破流的延迟评估.原则上,可变流源可以合法地任意改变直到终端动作开始时的点.结果将反映到目前为止所做的所有修改.当您添加一个合并的中间操作list.size(),即此时列表的实际大小时,在此点与终端操作之间应用于集合的后续修改可能会使此值具有与预期的"实际无限制"语义不同的含义.
与API文档的"非干扰"部分进行比较:
对于性能良好的流源,可以在终端操作开始之前修改源,并且这些修改将反映在所覆盖的元素中.例如,请考虑以下代码:
Run Code Online (Sandbox Code Playgroud)List<String> l = new ArrayList(Arrays.asList("one", "two")); Stream<String> sl = l.stream(); l.add("three"); String s = sl.collect(joining(" "));首先创建一个包含两个字符串的列表:"one"; 和"两个".然后从该列表创建流.接下来,通过添加第三个字符串来修改列表:"three".最后,收集流的元素并将它们连接在一起.由于在终端收集操作开始之前修改了列表,结果将是一串"一二三".
当然,这是一个罕见的极端情况,通常情况下,程序员将制定整个流管道而不修改其间的源集合.尽管如此,当你进入这样一个角落的情况时,不同的语义仍然存在并且它可能变成一个非常难以发现的错误.
此外,由于它们不是等价的,因此流API永远不会将这些值识别为"实际上没有限制".即使指定也Long.MAX_VALUE意味着流实现必须跟踪已处理元素的数量以确保遵守该限制.因此,limit与添加程序员期望永远不会超过的数量的限制相比,不添加操作可以具有显着的性能优势.
我认为你的第一行必须是:
stream = stream.filter(e -> e.getTimestamp() < max);
Run Code Online (Sandbox Code Playgroud)
这样您在后续操作中使用过滤器返回的流而不是原始流.
有两种方法可以做到这一点
// Do some stream stuff
List<E> results = list.stream()
.filter(e -> e.getTimestamp() < max);
.limit(limit > 0 ? limit : list.size())
.collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)
要么
// Do some stream stuff
stream = stream.filter(e -> e.getTimestamp() < max);
// Limit the stream
if (limit != -1) {
stream = stream.limit(limit);
}
// Collect stream to list
List<E> results = stream.collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)
由于这是函数式编程,因此您应始终处理每个函数的结果.您应该特别避免在这种编程风格中修改任何内容,并将所有内容视为可能的,如果它是不可变的.
由于我在调用终端操作之前重新分配了流的值,上面的代码仍然是使用Java 8流的正确方法吗?
它应该工作,但它读作命令式和功能性编码的混合.我建议根据我的第一个答案将其写为固定流.
| 归档时间: |
|
| 查看次数: |
17060 次 |
| 最近记录: |