Jak mogę sprawdzić, czy a Stream
jest puste i zgłosić wyjątek, jeśli tak nie jest, jako operacja nieterminalowa?
Zasadniczo szukam czegoś równoważnego z poniższym kodem, ale bez materializacji strumienia pomiędzy. W szczególności sprawdzenie nie powinno mieć miejsca przed faktycznym zużyciem strumienia przez operację terminala.
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
Głowonóg
źródło
źródło
Odpowiedzi:
Jeśli możesz żyć z ograniczonymi możliwościami równoległymi, zadziała następujące rozwiązanie:
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); }
Oto przykładowy kod, który go używa:
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);
Problem z (wydajnym) wykonywaniem równoległym polega na tym, że obsługa dzielenia
Spliterator
wymaga bezpiecznego wątkowo sposobu, aby zauważyć, czy którykolwiek z fragmentów widział jakąkolwiek wartość w sposób bezpieczny dla wątków. Następnie ostatni z wykonywanych fragmentówtryAdvance
musi zdać sobie sprawę, że jest ostatnim (i też nie mógł przejść dalej), który rzuci odpowiedni wyjątek. Więc nie dodałem tutaj obsługi dzielenia.źródło
Pozostałe odpowiedzi i komentarze są poprawne, ponieważ aby zbadać zawartość strumienia, należy dodać operację terminalową, tym samym „konsumując” strumień. Można to jednak zrobić i zamienić wynik z powrotem w strumień, bez buforowania całej zawartości strumienia. Oto kilka przykładów:
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()); } }
Zasadniczo zamień strumień w strumień,
Iterator
aby go wywołaćhasNext()
, a jeśli to prawda, zamieńIterator
tył w plikStream
. Jest to nieefektywne, ponieważ wszystkie kolejne operacje na strumieniu będą przechodzić przez IteratorhasNext()
inext()
metody, co również oznacza, że strumień jest skutecznie przetwarzany sekwencyjnie (nawet jeśli później zostanie włączony równolegle). Pozwala to jednak przetestować strumień bez buforowania wszystkich jego elementów.Prawdopodobnie istnieje sposób, aby to zrobić, używając
Spliterator
zamiastIterator
. Potencjalnie umożliwia to, aby zwrócony strumień miał takie same cechy jak strumień wejściowy, w tym działanie równoległe.źródło
estimatedSize
acharacteristics
nawet może poprawić wydajność jednowątkową. Tak się złożyło, że napisałemSpliterator
rozwiązanie, kiedy publikowałeśIterator
rozwiązanie…tryAdvance
przedtemStream
zamienia leniwą naturęStream
w „częściowo leniwy” strumień. Oznacza to również, że wyszukiwanie pierwszego elementu nie jest już operacją równoległą, ponieważ musisz najpierw podzielić się i jednocześnie wykonaćtryAdvance
na podzielonych częściach, aby wykonać prawdziwą operację równoległą, o ile rozumiem. Jeśli jedyna operacja terminalowa jestfindAny
lub podobna, spowoduje to zniszczenie całegoparallel()
żądania.tryAdvance
przed wykonaniem strumienia i musisz zawijać każdą podzieloną część do serwera proxy i samodzielnie zbierać informacje „hasAny” wszystkich operacji współbieżnych i upewnić się, że ostatnia operacja współbieżna zgłosi żądany wyjątek strumień był pusty. Dużo rzeczy…W wielu przypadkach może to być wystarczające
źródło
Aby zastosować którykolwiek z filtrów, musisz wykonać operację terminalową na strumieniu. Dlatego nie możesz wiedzieć, czy będzie pusty, dopóki go nie skonsumujesz.
Najlepsze, co możesz zrobić, to zakończyć strumień
findAny()
operacją terminalową, która zatrzyma się, gdy znajdzie dowolny element, ale jeśli nie ma żadnego, będzie musiał iterować po całej liście wejściowej, aby to sprawdzić.Pomogłoby to tylko wtedy, gdy lista wejściowa zawiera wiele elementów, a jeden z kilku pierwszych przechodzi przez filtry, ponieważ tylko niewielki podzbiór listy musiałby zostać wykorzystany, zanim zorientujesz się, że strumień nie jest pusty.
Oczywiście nadal będziesz musiał utworzyć nowy strumień, aby utworzyć listę wyników.
źródło
anyMatch(alwaysTrue())
, myślę, że jest najbliżejhasAny
.anyMatch(alwaysTrue())
idealnie pasuje do zamierzonej semantyki twojejhasAny
, dając ciboolean
zamiastOptional<T>
--- ale tutaj dzielimy włosy :)alwaysTrue
to predykat guawy.anyMatch(e -> true)
następnie.Myślę, że powinno wystarczyć do zmapowania wartości logicznej
W kodzie to jest:
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
źródło
Stream.anyMatch()
Zgodnie z pomysłem Stuarta można to zrobić w następujący sposób
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; } }
Myślę, że działa to z równoległymi strumieniami, ponieważ
stream.spliterator()
operacja zakończy strumień, a następnie przebuduje go zgodnie z wymaganiamiW moim przypadku potrzebowałem wartości domyślnej,
Stream
a nie domyślnej. to jest dość łatwe do zmiany, jeśli nie tego potrzebujeszźródło
Spliterator
i zastanawiam się, jak te dwa są porównane.Po prostu użyłbym:
stream.count()>0
źródło