Interface AsyncIterator<T>
- Type Parameters:
T- Type of object being iterated over.
- All Superinterfaces:
AsyncCloseable
A mechanism for asynchronously generating and consuming values
Consider this an async version of Stream.
AsyncIterators have lazy, pull based, evaluation semantics - values are not computed until they are needed. AsyncIterators are not immutable - like streams, each value they produce is consumed only once. Typically you should not apply multiple transformations to the same source AsyncIterator, it almost certainly won't do what you want it to do.
Implementors of the interface need only implement nextStage().
A note on thread safety: This class makes no assumption that nextStage() is thread
safe! Many methods that generate transformed iterators assume that nextStage will not be called
concurrently, and even stronger, that nextStage won't be called again until the previous stage
returned by nextStage has completed.
Parallelization may still be accomplished using the partially eager methods described below. The difference is that the parallelization in that case is from producing values in parallel, not consuming values in parallel.
To implement an AsyncIterator you must only implement the nextStage() method -
however, it is recommended that users avoid actually using nextStage to consume the results of
iteration. It is less expressive and it can also be error prone; it is easy to cause a stack
overflow by incorrectly recursing on calls to nextStage. You should prefer to use the other
higher level methods on this interface.
There are 2 main categories of such methods on this interface: Intermediate and Terminal.
These methods can be combined to form pipelines, which generally consist of a source (often
created with the static constructor methods on this interface (fromIterator(Iterator),
etc)), followed by zero or more intermediate operations (such
as filter(Predicate), thenApply(Function)), and completed with a terminal
operation which returns a CompletionStage (such as forEach(Consumer)).
For example, suppose we wanted to accomplish the following
(blocking) procedure:
// request and lookup records one by one until we get 10 relevant records
List<Record> records = new ArrayList<>()
while (records.size() < 10) {
// ask for a record identifier from a remote service (blocking)
RecordId response = requestIdFromIdServer();
// get the actual record from another service (blocking)
Record record = getRecordFromRecordServer(recordIdentifier);
// only add relevant records
if (isRelevant(record)) {
records.add(record);
}
}
If we wanted to do it without doing any blocking, we can use a pipeline and return a CompletionStage of the desired record list. Like the blocking version only one request will be
made at a time.
CompletionStage<RecordId> requestIdFromIdServer();
CompletionStage<Record> getRecordFromRecordServer(RecordId recordId);
CompletionStage<List<Response>> responses =
AsyncIterator.generate(this::requestIdFromIdServer) // source iterator
.thenCompose(this::getRecordFromRecordServer) // intermediate transformation
.filter(record -> isRelevant(record)) // intermediate transformation
.take(10) // intermediate transformation
.collect(Collectors.toList()); // terminal operation
Intermediate methods - All methods which return AsyncIterators
are intermediate methods. They can further be broken down into lazy and partially eager methods.
Methods that end with the suffix ahead are partially eager, the rest are lazy. A lazy
intermediate transformation will not be evaluated until some downstream eager operation is
called. Furthermore, only what is needed to satisfy the eager operation will be evaluated from
the previous iterator in the chain. When only requesting a single element from the transformed
iterator, only a single element may be evaluated from the previous iterator (ex: thenApply(Function)), or potentially many elements (ex: filter(Predicate)).
Methods ending with the suffix ahead , are partially eager. They can be used when there is an expensive transformation step that should be performed in parallel. They will eagerly consume from their upstream iterator up to a specified amount (still sequentially!) and eagerly apply the transformation step.
Intermediate methods will propagate exceptions similarly to CompletionStage, a
dependent AsyncIterator will return exceptional stages if the upstream iterator generated
exceptional elements.
Terminal methods - Terminal methods consume the iterator and return a CompletionStage. After a terminal operation is called, the iterator is considered consumed and
should not be used further. If any of the stages in the chain that comprise this iterator
were exceptional, the CompletionStage returned by a terminal operation will also be
exceptional. The exception will short-circuit the terminal operation. For example, a terminal
operation such as forEach(Consumer) will not to continue to run on subsequent elements
of the iterator and instead immediately complete its returned stage with the error. Unless
otherwise noted, this behavior holds for all terminal methods but may not documented explicitly.
The exception propagation scheme should be familiar to users of CompletionStage,
upstream errors will appear wherever the AsyncIterator is consumed and the result is observed
(with CompletableFuture.join() for instance).
Unless otherwise noted, methods on this interface are free to throw NullPointerException if any of the provided arguments are null.
The behavior of an AsyncIterator if nextStage() is called after the end of iteration
marker is returned is left to the implementation.
This interface extends AsyncCloseable, if there
are resources associated with this iterator that must be relinquished after iteration is
complete, the close() method should be implemented. Because the majority of methods do
not have a manually managed resource, a default implementation of close which does nothing is
provided. Terminal methods on this interface do not call close(), it is generally the
user's responsibility..
- See Also:
-
Nested Class Summary
Nested ClassesModifier and TypeInterfaceDescriptionstatic enumA marker enum that indicates there are no elements left in the iterator. -
Method Summary
Modifier and TypeMethodDescriptiondefault <A,R> AsyncIterator<R> A convenience method provided to invokebatch(Collector, BiPredicate)with a predicate that limits batches to a fixed size.default <A,R> AsyncIterator<R> batch(Collector<? super T, A, R> collector, BiPredicate<? super A, ? super T> shouldAddToBatch) Collects the results of this iterator in batches, returning an iterator of those batched collections.default CompletionStage<Void>close()Relinquishes any resources associated with this iterator.default <R> CompletionStage<R>collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator) Performs a mutable reduction operation and return aCompletionStageof the result.default <R,A> CompletionStage<R> Performs a mutable reduction operation using collector and return a CompletionStage of the result.static <T> AsyncIterator<T>empty()Creates an empty AsyncIterator.default AsyncIterator<T>Transforms the AsyncIterator into one which will only produce results that matchpredicate.default CompletionStage<Optional<T>>Gets the first element that satisfies predicate, or empty if no such element existsdefault <U> CompletionStage<U>fold(U identity, BiFunction<U, ? super T, U> accumulator) Sequentially accumulates the elements of type T in this iterator into a U.default CompletionStage<Void>Performs the side effecting action until the end of iteration is reachedstatic <T> AsyncIterator<T>fromIterator(Iterator<? extends T> iterator) Creates an AsyncIterator from anIteratorstatic <T> AsyncIterator<T>generate(Supplier<? extends CompletionStage<T>> supplier) Creates an infinite AsyncIterator of type T.Returns a stage that will be completed with the next element ofthisiterator when it becomes available, orAsyncIterator.Endif there are no more elements.static AsyncIterator<Long>range(long start, long end) Creates an AsyncIterator for a range.default <U> AsyncIterator<U>Transformsthisinto a new AsyncIterator that iterates over the results offnapplied to the outcomes of stages in this iterator when they complete normally.default <U> AsyncIterator<U>thenCompose(Function<? super T, ? extends CompletionStage<U>> fn) Transformsthisinto a new AsyncIterator using the produced stages offnapplied to the output from the stages ofthis.
-
Method Details
-
nextStage
CompletionStage<Either<AsyncIterator.End,T>> nextStage()Returns a stage that will be completed with the next element ofthisiterator when it becomes available, orAsyncIterator.Endif there are no more elements.This is not a terminal method, it can be safely called multiple times. However, this method is not thread safe, and should only be called in a single-threaded fashion. Moreover, sequential calls should not be made until the
CompletionStagereturned by the previous call has completed. That is to say,// illegal pool.execute(() -> nextStage()) pool.execute(() -> nextStage()) // just as illegal f1 = nextStage(); f2 = nextStage(); // good nextStage().thenCompose(t -> nextStage());Though this is not a terminal method, if a terminal method has been called it is no longer safe to call this method. When nextStage returns
AsyncIterator.End, the iterator has no more elements. After an iterator emits anAsyncIterator.Endindicator, the result of subsequent calls to nextStage is undefined.An AsyncIterator may be capable of producing normally completing stages after having producing exceptionally completed stages. nextStage is unique in that it can safely continue to be called even after a returned stage completes exceptionally, whereas all terminal operations short circuit when encountering an exception. If a user wishes to continue iteration after exception, they must use nextStage directly.
- Returns:
- A
CompletionStageof the next element for iteration held in theEither.right()position, or an instance ofAsyncIterator.Endheld in theEither.left()position indicating the end of iteration.
-
close
Relinquishes any resources associated with this iterator.This method should be overridden if manual resource management is required, the default implementation does nothing. This method is not thread safe, and must not be called concurrently with calls to
nextStage(). This method is not automatically called by terminal methods, and must be explicitly called after iteration is complete if the underlying iterator has resources to release. Similar to the situation withBaseStream.close(), because the common case requires no resources the user should only call close if it is possible that theAsyncIteratorhas resources. Special care needs to be taken to call close even in the case of an exception.class SocketBackedIterator implements AsyncIterator<byte[]> { ... {@literal @Override} CompletionStage<Void> close() { return socket.close(); } } AsyncCloseable.tryComposeWith(new SocketBackedIterator(socket), socketIt -> socketIt .thenCompose(this::deserialize) .filter(this::isRelevantMessage) .forEach(message -> System.out.println(message)));Intermediate methods will pass calls to close to their upstream iterators, so it is safe to call close on an intermediate result of an iterator instead of on it directly. For example,
AsyncIterator<byte[]> original = new SocketBackedIterator(socket); AsyncIterator<Message> transformed = original.thenCompose(this::deserialize).filter(this::isRelevantMessage); transformed.close() // will close on original- Specified by:
closein interfaceAsyncCloseable- Returns:
- a
CompletionStagethat completes when all resources associated with this iterator have been relinquished.
-
thenApply
Transformsthisinto a new AsyncIterator that iterates over the results offnapplied to the outcomes of stages in this iterator when they complete normally. When stages inthisiterator complete exceptionally the returned iterator will emit an exceptional stage without applyingfn.intIterator // 1,2,3,... .thenApply(Integer::toString) //"1","2","3"...This is a lazy intermediate method.
- Parameters:
fn- A function which produces a U from the given T- Returns:
- A new AsyncIterator which produces stages of fn applied to the result of the stages
from
thisiterator
-
thenCompose
Transformsthisinto a new AsyncIterator using the produced stages offnapplied to the output from the stages ofthis. When stages inthisiterator complete exceptionally the returned iterator will emit an exceptional stage without applyingfn.CompletableFuture<String> asyncToString(final int i); intIterator // 1, 2, 3 .thenCompose(this::asyncToString); //"1", "2", "3"...This is a lazy intermediate method.
- Parameters:
fn- A function which produces a newCompletionStagefrom a T- Returns:
- A new AsyncIterator which produces stages of fn composed with the result of the stages
from
thisiterator
-
filter
Transforms the AsyncIterator into one which will only produce results that matchpredicate.This is a lazy intermediate method.
- Parameters:
predicate- A function that takes a T and returns true if it should be returned by the new iterator, and false otherwise- Returns:
- a new AsyncIterator which will only return results that match predicate
-
batch
default <A,R> AsyncIterator<R> batch(Collector<? super T, A, R> collector, BiPredicate<? super A, ? super T> shouldAddToBatch) Collects the results of this iterator in batches, returning an iterator of those batched collections.This may be useful for performing bulk operations on many elements, rather than on one element at a time.
This is a lazy intermediate method.
- Parameters:
collector- aCollectorused to collect the elements of this iterator into individual batches. Each batch will be created by invoking the collector'sCollector.supplier()methodshouldAddToBatch- a predicate which determines whether a given element encountered during iteration should be added to the given (current) batch. If this predicate returns true for the given element and container, the element will beaddedto the container, and the batching operation will continue to draw from the underlying iterator. If this predicate returns false, the element will not be added and the current batch will befinishedand returned by the batching iterator. The element which did not meet the predicate will be tested again by the next batch- Returns:
- an AsyncIterator which invokes several iterations of the underlying iterator with each
advance, collecting these elements into containers provided by the given
Collector.
-
batch
A convenience method provided to invokebatch(Collector, BiPredicate)with a predicate that limits batches to a fixed size.Each batch will be as large as the given
batchSizeexcept possibly the last one, which may be smaller due to exhausting the underlying iterator.This is a lazy intermediate method.
- See Also:
-
fold
Sequentially accumulates the elements of type T in this iterator into a U. This provides an immutable style terminal reduction operation as opposed to the mutable style supported bycollect(java.util.stream.Collector<? super T, A, R>). For example, to sum the lengths of Strings in an AsyncIterator,stringIt.fold(0, (acc, s) -> acc + s.length()).This is a terminal method.
- Parameters:
accumulator- a function that produces a new accumulation from an existing accumulation and a new elementidentity- a starting U value- Returns:
- a
CompletionStagecontaining the resulting U from repeated application of accumulator
-
collect
Performs a mutable reduction operation using collector and return a CompletionStage of the result.This is a terminal method.
- Type Parameters:
A- The intermediate type of the accumulated objectR- The final type of the accumulated object- Parameters:
collector- aCollectorwhich will sequentially collect the contents of this iterator into anR- Returns:
- a
CompletionStagewhich will complete with the collected value - See Also:
-
collect
Performs a mutable reduction operation and return aCompletionStageof the result. A mutable reduction is one where the accumulator has mutable state and additional elements are incorporated by updating that state.This is a terminal method.
- Parameters:
supplier- a supplier for a stateful accumulatoraccumulator- a function which can incorporate T elements into a stateful accumulation- Returns:
- a
CompletionStagewhich will complete with the accumulated value - See Also:
-
range
Creates an AsyncIterator for a range.Similar to
for(i = start; i < end; i++).The stages returned by nextStage will be already completed.
- Parameters:
start- the start point of iteration (inclusive)end- the end point of iteration (exclusive)- Returns:
- an AsyncIterator that will return longs from start to end
-
forEach
Performs the side effecting action until the end of iteration is reachedThis is a terminal method.
- Parameters:
action- a side-effecting action that takes a T- Returns:
- a
CompletionStagethat returns when there are no elements left to applyactionto, or an exception has been encountered.
-
find
Gets the first element that satisfies predicate, or empty if no such element existsThis is a terminal method.
- Parameters:
predicate- the predicate that returns true for the desired element- Returns:
- a
CompletionStagethat completes with the first T to satisfy predicate, or empty if no such T exists
-
empty
Creates an empty AsyncIterator.- Returns:
- an AsyncIterator that will immediately produce an
AsyncIterator.Endmarker
-
fromIterator
Creates an AsyncIterator from anIterator- Parameters:
iterator- anIteratorof T elements- Returns:
- A new AsyncIterator which will yield the elements of
iterator
-
generate
Creates an infinite AsyncIterator of type T.- Parameters:
supplier- supplies stages for elements to be yielded by the returned iterator- Returns:
- AsyncIterator returning values generated from
supplier
-