顺序流操作会产生副作用吗?
Can sequential stream operations have side-effects?
我正在尝试将有限数量的值流式传输到集合中,但我需要在应用限制之前验证它们是新元素。例如:
Set<Integer> destination = ...
Set<Integer> source = ...
source.stream()
.filter(i -> !destination.contains(i))
.limit(10)
.forEach(destination::add);
但是多余的 contains()
检查让我很困扰,因为 add()
可以同时添加元素 和 报告它是否是集合中的新元素。所以我想这样做:
source.stream()
.filter(destination::add)
.limit(10)
.forEach(i -> {}); // no-op terminal operation to force evaluation
忽略 hacky 终端操作,使用具有副作用的过滤器操作存在问题,通常不鼓励这样做。我理解为什么使用 map()
和 filter()
对并行流有副作用是不安全的。我的问题是,在这种情况下,在顺序流上是否可以接受?如果不是,为什么不呢?
副作用和顺序流没有根本问题,但是上面的第二种实现是无效的,因为流API不保证每个阶段都会在每个元素上执行依次.
在第二个实现中,在应用限制之前可以向 destination
添加 10 个以上的元素。您的 no-op forEach 只会看到 10 个,但您最终可能会得到更多。
除了流之外,java 有像 for
和 while
这样的循环结构,可以让表达这样的事情变得容易。
如果您必须使用流,您可以这样做:
int maxSize = destination.size()+10;
source.stream().allMatch(x -> destination.size()<maxsize && (destination.add(x)||true));
一旦谓词returns为假,allMatch
就会停止迭代。
这对你有用吗:
package be.objectsmith;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class Playground {
public static void main(String[] args) {
copy(
IntStream.range(1, 20).boxed().collect(Collectors.toSet()),
new HashSet<>(Arrays.asList(2, 5)));
copy(
IntStream.range(1, 5).boxed().collect(Collectors.toSet()),
new HashSet<>(Arrays.asList(2, 5)));
}
private static void copy(Set<Integer> source, Set<Integer> destination) {
source
.stream()
.map(destination::add)
.filter(resultOfAdding -> resultOfAdding)
.limit(10)
.collect(Collectors.toList()); // Need a terminal operation
System.out.println("source = " + source);
System.out.println("destination = " + destination);
}
}
运行 这个 class 将打印:
source = [1, 2, 3, 4]
destination = [1, 2, 3, 4, 5]
source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
destination = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
如您所见,它只添加了 10 个元素。
第二次调用表明,如果要添加的元素少于 10 个,它也可以工作。
我正在尝试将有限数量的值流式传输到集合中,但我需要在应用限制之前验证它们是新元素。例如:
Set<Integer> destination = ...
Set<Integer> source = ...
source.stream()
.filter(i -> !destination.contains(i))
.limit(10)
.forEach(destination::add);
但是多余的 contains()
检查让我很困扰,因为 add()
可以同时添加元素 和 报告它是否是集合中的新元素。所以我想这样做:
source.stream()
.filter(destination::add)
.limit(10)
.forEach(i -> {}); // no-op terminal operation to force evaluation
忽略 hacky 终端操作,使用具有副作用的过滤器操作存在问题,通常不鼓励这样做。我理解为什么使用 map()
和 filter()
对并行流有副作用是不安全的。我的问题是,在这种情况下,在顺序流上是否可以接受?如果不是,为什么不呢?
副作用和顺序流没有根本问题,但是上面的第二种实现是无效的,因为流API不保证每个阶段都会在每个元素上执行依次.
在第二个实现中,在应用限制之前可以向 destination
添加 10 个以上的元素。您的 no-op forEach 只会看到 10 个,但您最终可能会得到更多。
除了流之外,java 有像 for
和 while
这样的循环结构,可以让表达这样的事情变得容易。
如果您必须使用流,您可以这样做:
int maxSize = destination.size()+10;
source.stream().allMatch(x -> destination.size()<maxsize && (destination.add(x)||true));
一旦谓词returns为假,allMatch
就会停止迭代。
这对你有用吗:
package be.objectsmith;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class Playground {
public static void main(String[] args) {
copy(
IntStream.range(1, 20).boxed().collect(Collectors.toSet()),
new HashSet<>(Arrays.asList(2, 5)));
copy(
IntStream.range(1, 5).boxed().collect(Collectors.toSet()),
new HashSet<>(Arrays.asList(2, 5)));
}
private static void copy(Set<Integer> source, Set<Integer> destination) {
source
.stream()
.map(destination::add)
.filter(resultOfAdding -> resultOfAdding)
.limit(10)
.collect(Collectors.toList()); // Need a terminal operation
System.out.println("source = " + source);
System.out.println("destination = " + destination);
}
}
运行 这个 class 将打印:
source = [1, 2, 3, 4]
destination = [1, 2, 3, 4, 5]
source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
destination = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
如您所见,它只添加了 10 个元素。 第二次调用表明,如果要添加的元素少于 10 个,它也可以工作。