Как я могу проверить, является ли a Stream
пустым, и вызвать исключение, если это не так, как нетерминальную операцию?
По сути, я ищу что-то эквивалентное приведенному ниже коду, но без материализации промежуточного потока. В частности, проверка не должна выполняться до того, как поток будет фактически использован операцией терминала.
public Stream<Thing> getFilteredThings() {
Stream<Thing> stream = getThings().stream()
.filter(Thing::isFoo)
.filter(Thing::isBar);
return nonEmptyStream(stream, () -> {
throw new RuntimeException("No foo bar things available")
});
}
private static <T> Stream<T> nonEmptyStream(Stream<T> stream, Supplier<T> defaultValue) {
List<T> list = stream.collect(Collectors.toList());
if (list.isEmpty()) list.add(defaultValue.get());
return list.stream();
}
java
java-8
java-stream
Головоногие моллюски
источник
источник
Ответы:
Если вы можете жить с ограниченными параллельными возможностями, следующее решение будет работать:
private static <T> Stream<T> nonEmptyStream( Stream<T> stream, Supplier<RuntimeException> e) { Spliterator<T> it=stream.spliterator(); return StreamSupport.stream(new Spliterator<T>() { boolean seen; public boolean tryAdvance(Consumer<? super T> action) { boolean r=it.tryAdvance(action); if(!seen && !r) throw e.get(); seen=true; return r; } public Spliterator<T> trySplit() { return null; } public long estimateSize() { return it.estimateSize(); } public int characteristics() { return it.characteristics(); } }, false); }
Вот пример кода с его использованием:
List<String> l=Arrays.asList("hello", "world"); nonEmptyStream(l.stream(), ()->new RuntimeException("No strings available")) .forEach(System.out::println); nonEmptyStream(l.stream().filter(s->s.startsWith("x")), ()->new RuntimeException("No strings available")) .forEach(System.out::println);
Проблема с (эффективным) параллельным выполнением заключается в том, что для поддержки разделения
Spliterator
требуется потокобезопасный способ заметить, видел ли какой-либо из фрагментов какое-либо значение поточно-безопасным способом. Затем последний из выполняемых фрагментовtryAdvance
должен понять, что он последний (и он также не может продвинуться), чтобы выбросить соответствующее исключение. Поэтому я не добавил сюда поддержку разделения.источник
Остальные ответы и комментарии верны в том, что для проверки содержимого потока необходимо добавить операцию терминала, тем самым «потребляя» поток. Однако можно сделать это и превратить результат обратно в поток без буферизации всего содержимого потока. Вот пара примеров:
static <T> Stream<T> throwIfEmpty(Stream<T> stream) { Iterator<T> iterator = stream.iterator(); if (iterator.hasNext()) { return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false); } else { throw new NoSuchElementException("empty stream"); } } static <T> Stream<T> defaultIfEmpty(Stream<T> stream, Supplier<T> supplier) { Iterator<T> iterator = stream.iterator(); if (iterator.hasNext()) { return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false); } else { return Stream.of(supplier.get()); } }
В основном превратите поток в объект
Iterator
, чтобы вызватьhasNext()
его, и, если это правда, превратите егоIterator
обратно вStream
. Это неэффективно в том смысле, что все последующие операции с потоком будут проходить через IteratorhasNext()
иnext()
методы, что также подразумевает, что поток эффективно обрабатывается последовательно (даже если он позже станет параллельным). Однако это позволяет вам тестировать поток без буферизации всех его элементов.Вероятно, есть способ сделать это, используя
Spliterator
вместоIterator
. Это потенциально позволяет возвращаемому потоку иметь те же характеристики, что и входной поток, в том числе работать параллельно.источник
estimatedSize
иcharacteristics
могу даже улучшить однопоточную производительность. Просто так получилось, что я написалSpliterator
решение, пока вы публиковалиIterator
решение…tryAdvance
до того,Stream
как это делает, превращает ленивую природу классаStream
в «частично ленивый» поток. Это также подразумевает, что поиск первого элемента больше не являетсяtryAdvance
параллельной операцией, поскольку, насколько я понял, вам нужно сначала разделить и сделать на разделенных частях одновременно, чтобы выполнить настоящую параллельную операцию. Если единственная операция терминала являетсяfindAny
или аналогичной, это уничтожит весьparallel()
запрос.tryAdvance
до того, как это сделает поток, и должны заключить каждую разделенную часть в прокси и собрать информацию «hasAny» обо всех параллельных операциях самостоятельно и убедиться, что последняя параллельная операция выдает желаемое исключение, если поток был пуст. Много всего…Во многих случаях этого может быть достаточно
источник
Вы должны выполнить терминальную операцию над потоком, чтобы применить какой-либо из фильтров. Следовательно, вы не можете узнать, будет ли он пустым, пока вы его не съедите.
Лучшее, что вы можете сделать, - это завершить Stream с помощью
findAny()
терминальной операции, которая остановится, когда он найдет какой-либо элемент, но если его нет, ему придется перебирать весь входной список, чтобы выяснить это.Это поможет вам только в том случае, если входной список содержит много элементов, и один из первых нескольких элементов проходит фильтры, так как только небольшое подмножество списка должно быть использовано, прежде чем вы узнаете, что поток не пуст.
Конечно, вам все равно придется создать новый поток, чтобы создать выходной список.
источник
anyMatch(alwaysTrue())
, я думаю , что это ближе всего кhasAny
.anyMatch(alwaysTrue())
идеально соответствует предполагаемой семантике вашегоhasAny
, давая вамboolean
вместоOptional<T>
--- но мы тутalwaysTrue
- это предикат Guava.anyMatch(e -> true)
тогда.Я думаю, этого должно быть достаточно, чтобы сопоставить логическое значение
В коде это:
boolean isEmpty = anyCollection.stream() .filter(p -> someFilter(p)) // Add my filter .map(p -> Boolean.TRUE) // For each element after filter, map to a TRUE .findAny() // Get any TRUE .orElse(Boolean.FALSE); // If there is no match return false
источник
Stream.anyMatch()
Следуя идее Стюарта, это можно сделать
Spliterator
так:static <T> Stream<T> defaultIfEmpty(Stream<T> stream, Stream<T> defaultStream) { final Spliterator<T> spliterator = stream.spliterator(); final AtomicReference<T> reference = new AtomicReference<>(); if (spliterator.tryAdvance(reference::set)) { return Stream.concat(Stream.of(reference.get()), StreamSupport.stream(spliterator, stream.isParallel())); } else { return defaultStream; } }
Я думаю, что это работает с параллельными потоками, так как
stream.spliterator()
операция завершит поток, а затем перестроит его по мере необходимости.В моем случае использования мне нужно было значение по умолчанию,
Stream
а не значение по умолчанию. это довольно легко изменить, если это не то, что вам нужноисточник
Spliterator
мне интересно, как они сравниваются.Я бы просто использовал:
stream.count()>0
источник