例如,我需要这样的东西:
Collection<String> collection = /* ... */;
Stream<Object> stream = /* ... */;
boolean containsAll = stream.map(Object::toString).containsAll(collection);
当然,我可以使用 collect()
方法和调用 Collection.containsAll()
将流的所有元素累积到另一个 Collection
>,但是如果流太大并且处理它的所有元素效率低下怎么办?
最佳答案
这应该可以解决问题:
Set<String> set = new HashSet<>(collection);
boolean containsAll = set.isEmpty() || stream.map(Object::toString)
.anyMatch(s -> set.remove(s) && set.isEmpty());
解决方案可能看起来令人困惑,但想法很简单:
- 为了防止对
collection
进行多次迭代,我们将其包装到HashSet
中。 (如果您的stream
是并行的,那么您将不得不使用并发哈希集。有关详细信息,请参阅 this post) - 如果
collection
(或set
)为空,则我们返回true
而不处理stream
< - 对于
stream
的每个条目,我们尝试将其从set
中删除。如果Set::remove
的结果是true
(因此它包含在set
中)并且set
删除后为空,我们可以得出结论stream
包含初始collection
的所有元素。 - 终端操作
Stream::anyMatch
是一个短路操作。因此,一旦set
为空,它将停止遍历stream
。在最坏的情况下,我们将处理整个流。
也许这是一种更具可读性的形式:
Set<String> set = new HashSet<>(collection);
boolean containsAll = set.isEmpty() || stream.map(Object::toString)
.filter(set::remove)
.anyMatch(__ -> set.isEmpty());
如果 collection
可以包含重复项,并且需要检查 stream
是否包含所有重复项,那么我们将需要维护一个并发计数器映射。
Map<String, AtomicLong> map = new ConcurrentHashMap<>();
collection.forEach(s -> map.computeIfAbsent(s, __ -> new AtomicLong()).incrementAndGet());
boolean containsAll = map.isEmpty() || stream.map(Object::toString)
.filter(map::containsKey)
.filter(s -> map.get(s).decrementAndGet() == 0)
.filter(s -> map.remove(s) != null)
.anyMatch(__ -> map.isEmpty());
代码略有变化,但思路是一样的。
关于java - 有没有办法检查 Stream 是否包含所有集合元素?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58269632/