有没有一种方法可以检查Stream是否包含所有集合元素?

Nol*_*uen 6 java collections contains java-8 java-stream

例如,我需要类似的东西:

Collection<String> collection = /* ... */;
Stream<Object> stream = /* ... */;
boolean containsAll = stream.map(Object::toString).containsAll(collection);
Run Code Online (Sandbox Code Playgroud)

当然,我可以Collection使用collect()方法和调用将流的所有元素累积到另一个元素中Collection.containsAll(),但是如果流太大并且处理所有元素的效率低下怎么办?

ETO*_*ETO 8

这应该可以解决问题:

Set<String> set = new HashSet<>(collection);
boolean containsAll = set.isEmpty() || stream.map(Object::toString)
                                             .anyMatch(s -> set.remove(s) && set.isEmpty());
Run Code Online (Sandbox Code Playgroud)

解决方案可能看起来令人困惑,但想法很简单:

  1. 为了防止多次迭代,collection我们将它包装成一个HashSet. (如果您stream是并行的,那么您将不得不使用并发散列集。有关更多详细信息,请参阅此帖子
  2. 如果collection(or set) 为空,则我们返回true而不处理stream
  3. 对于stream我们尝试将其从set. 如果结果Set::removeis true(因此被 包含set)并且set删除后the是空的,我们可以得出结论stream包含 initial 的所有元素collection
  4. 端子操作Stream::anyMatch是短路操作。因此,stream一旦set为空,它将停止迭代。在最坏的情况下,我们将处理整个流。

也许这是一种更具可读性的形式:

Set<String> set = new HashSet<>(collection);
boolean containsAll = set.isEmpty() || stream.map(Object::toString)
                                             .filter(set::remove)
                                             .anyMatch(__ -> set.isEmpty());
Run Code Online (Sandbox Code Playgroud)

如果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());
Run Code Online (Sandbox Code Playgroud)

代码略有变化,但思路是一样的。