有条件地向Java 8流添加操作 [英] Conditionally add an operation to a Java 8 stream

查看:78
本文介绍了有条件地向Java 8流添加操作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道是否可以根据流外部设置的某种条件向流添加操作.例如,如果我的limit变量不等于-1,我想向流中添加一个限制操作.

I'm wondering if I can add an operation to a stream, based off of some sort of condition set outside of the stream. For example, I want to add a limit operation to the stream if my limit variable is not equal to -1.

我的代码当前看起来像这样,但是我还没有看到以这种方式使用流的其他示例,其中将Stream对象重新分配给对其自身执行中间操作的结果:

My code currently looks like this, but I have yet to see other examples of streams being used this way, where a Stream object is reassigned to the result of an intermediate operation applied on itself:

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

如该 stackoverflow帖子所述,该过滤器直到终端才实际应用操作被称为.由于我是在调用终端操作之前重新分配流的值,因此上面的代码仍然是使用Java 8流的正确方法吗?

As stated in this stackoverflow post, the filter isn't actually applied until a terminal operation is called. Since I'm reassigning the value of stream before a terminal operation is called, is the above code still a proper way to use Java 8 streams?

推荐答案

在一系列链接的调用和一系列存储中间返回值的调用之间没有语义上的区别.因此,以下代码片段是等效的:

There is no semantic difference between a chained series of invocations and a series of invocations storing the intermediate return values. Thus, the following code fragments are equivalent:

a = object.foo();
b = a.bar();
c = b.baz();

c = object.foo().bar().baz();

在任何一种情况下,每个方法都是在上一次调用的结果上调用的.但是在后一种情况下,中间结果不会存储,但在下一次调用时会丢失.就流API而言,调用下一个方法之后,不得使用中间结果 ,因此链接是使用流的自然方法,因为它本质上确保了您不会在返回的引用上调用多个方法.

In either case, each method is invoked on the result of the previous invocation. But in the latter case, the intermediate results are not stored but lost on the next invocation. In the case of the stream API, the intermediate results must not be used after you have called the next method on it, thus chaining is the natural way of using stream as it intrinsically ensures that you don’t invoke more than one method on a returned reference.

不过,只要遵守不多次使用返回的引用的约定,将引用存储到流中就不会错.通过按照问题中的方式使用它,即用下一次调用的结果覆盖变量,还可以确保对返回的引用不调用多个方法,因此这是正确的用法.当然,这仅适用于相同类型的中间结果,因此,当您使用mapflatMap时,如果要获取其他引用类型的流,则无法覆盖局部变量.然后,您必须小心,不要再次使用旧的局部变量,但是,如上所述,只要您在下一次调用后不使用它,中间存储就不会有任何问题.

Still, it is not wrong to store the reference to a stream as long as you obey the contract of not using a returned reference more than once. By using it they way as in your question, i.e. overwriting the variable with the result of the next invocation, you also ensure that you don’t invoke more than one method on a returned reference, thus, it’s a correct usage. Of course, this only works with intermediate results of the same type, so when you are using map or flatMap, getting a stream of a different reference type, you can’t overwrite the local variable. Then you have to be careful to not use the old local variable again, but, as said, as long as you are not using it after the next invocation, there is nothing wrong with the intermediate storage.

有时候,您已经进行存储,例如

Sometimes, you have to store it, e.g.

try(Stream<String> stream = Files.lines(Paths.get("myFile.txt"))) {
    stream.filter(s -> !s.isEmpty()).forEach(System.out::println);
}

请注意,该代码等效于以下替代方法:

Note that the code is equivalent to the following alternatives:

try(Stream<String> stream = Files.lines(Paths.get("myFile.txt")).filter(s->!s.isEmpty())) {
    stream.forEach(System.out::println);
}

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

它们是等效的,因为总是在filter的结果上调用forEach,而在Files.lines的结果上总是调用filter,并且最终的close()操作在关闭时被调用是在哪个结果上都没有关系影响整个流管道.

They are equivalent because forEach is always invoked on the result of filter which is always invoked on the result of Files.lines and it doesn’t matter on which result the final close() operation is invoked as closing affects the entire stream pipeline.

用一句话来说,就是正确的用法.

To put it in one sentence, the way you use it, is correct.

我什至更喜欢这样做,因为当您不想应用限制时不链接limit操作是表达您意图的最干净的方法.还值得注意的是,建议的替代方案可能在很多情况下都可以使用,但在语义上不是

I even prefer to do it that way, as not chaining a limit operation when you don’t want to apply a limit is the cleanest way of expression your intent. It’s also worth noting that the suggested alternatives may work in a lot of cases, but they are not semantically equivalent:

.limit(condition? aLimit: Long.MAX_VALUE)

假定您可能遇到的最大元素数是Long.MAX_VALUE,但是流中可以包含的元素数可能更多,甚至可能是无限的.

assumes that the maximum number of elements, you can ever encounter, is Long.MAX_VALUE but streams can have more elements than that, they even might be infinite.

.limit(condition? aLimit: list.size())

当流源为list时,将中断对流的惰性评估.原则上,可变的流源可能会合法地任意更改,直到终止操作开始时为止.结果将反映到此为止所做的所有修改.当您添加包含list.size()的中间操作(即列表的实际大小)时,对该点和终端操作之间的集合进行的后续修改可能会使此值具有与预期的实际上不"相同的含义.限制"的语义.

when the stream source is list, is breaking the lazy evaluation of a stream. In principle, a mutable stream source might legally get arbitrarily changed up to the point when the terminal action is commenced. The result will reflect all modifications made up to this point. When you add an intermediate operation incorporating list.size(), i.e. the actual size of the list at this point, subsequent modifications applied to the collection between this point and the terminal operation may turn this value to have a different meaning than the intended "actually no limit" semantic.

非API文档中的干扰"部分:

对于行为良好的流源,可以在终端操作开始之前对其进行修改,并且这些修改将反映在所涵盖的元素中.例如,考虑以下代码:

For well-behaved stream sources, the source can be modified before the terminal operation commences and those modifications will be reflected in the covered elements. For example, consider the following code:

List<String> l = new ArrayList(Arrays.asList("one", "two"));
Stream<String> sl = l.stream();
l.add("three");
String s = sl.collect(joining(" "));

首先创建一个列表,该列表由两个字符串组成:一个";和两个".然后从该列表创建一个流.接下来,通过添加第三个字符串三"来修改列表.最终,将流中的元素收集起来并结合在一起.由于该列表是在终端收集操作开始之前修改的,因此结果将是字符串一二三".

First a list is created consisting of two strings: "one"; and "two". Then a stream is created from that list. Next the list is modified by adding a third string: "three". Finally the elements of the stream are collected and joined together. Since the list was modified before the terminal collect operation commenced the result will be a string of "one two three".

当然,这是一种罕见的极端情况,通常情况下,程序员将制定整个流管道,而无需在其间修改源集合.尽管如此,仍然保留了不同的语义,并且一旦输入这种极端情况,它可能会变得非常难以发现错误.

Of course, this is a rare corner case as normally, a programmer will formulate an entire stream pipeline without modifying the source collection in between. Still, the different semantic remains and it might turn into a very hard to find bug when you once enter such a corner case.

此外,由于它们不相等,因此流API永远不会将这些值识别为实际上没有限制".甚至指定Long.MAX_VALUE都意味着流实现必须跟踪已处理元素的数量以确保已遵守限制.因此,不添加limit操作相对于添加限制(程序员希望永远不会超过该数字)可以具有显着的性能优势.

Further, since they are not equivalent, the stream API will never recognize these values as "actually no limit". Even specifying Long.MAX_VALUE implies that the stream implementation has to track the number of processed elements to ensure that the limit has been obeyed. Thus, not adding a limit operation can have a significant performance advantage over adding a limit with a number that the programmer expects to never be exceeded.

这篇关于有条件地向Java 8流添加操作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆