Это должно сработать:
Set<String> set = new HashSet<>(collection);
boolean containsAll = set.isEmpty() || stream.map(Object::toString)
.anyMatch(s -> set.remove(s) && set.isEmpty());
Решение может показаться запутанным, но идея проста:
- Чтобы предотвратить повторение нескольких итераций
collection
мы завернем в HashSet
. (В случае, если ваш stream
является параллельным, вам придется использовать параллельный хэш-набор. Подробнее см. этот пост ) - Если
collection
(или set
) пусто, тогда мы возвращаем true
без обработки stream
- . Для каждой записи
stream
мы пытаемся удалить ее из set
. Если результат Set::remove
равен true
(следовательно, он содержался в set
), а set
пуст после удаления, мы можем заключить, что stream
содержал все элементы исходного collection
. - Работа терминала
Stream::anyMatch
является короткозамкнутой. Поэтому он прекратит итерацию по stream
, когда set
станет пустым. В худшем случае мы обработаем весь поток.
Возможно, это немного более читаемая форма:
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());
Код немного изменился, но идея та же.