Imagine I\'m building a library, that will receive a Stream of Integers, and all the library code needs to do is return a stream of Strings with the string representation of
The error occurs in the stream intermediate operation, a clever way like as you to solving the problem is using the Proxy Design Pattern. for using the stream api you just need to proxying an Iterator
from the source Stream
to another Stream
by StreamSupport#stream & Spliterators#spliterator(Iterator, long, int) , for example:
Stream<String> result = convertToString(Stream.of("1", "bad", "2")
.map(Integer::parseInt));
public Stream<String> convertToString(Stream<Integer> input) {
return exceptionally(input, (e, action) -> action.accept(null))
.map(it -> String.format("%s", it == null ? "NaN" : it));
}
Current version Stream is base on Iterator
that fixed the Stream.of(T)
bug, for more details please see my question.
<T> Stream<T> exceptionally(Stream<T> source,
BiConsumer<Exception, Consumer<? super T>> handler) {
Spliterator<T> s = source.spliterator();
return StreamSupport.stream(
spliterator(
exceptionally(s, handler),
s.estimateSize(),
s.characteristics()
),
source.isParallel()
).onClose(source::close);
}
//Don't worried the thread-safe & robust since it is invisible for anyone
private <T> Iterator<T> exceptionally(Spliterator<T> spliterator,
BiConsumer<Exception, Consumer<? super T>> handler) {
class ExceptionallyIterator implements Iterator<T>, Consumer<T> {
private Iterator<T> source = Spliterators.iterator(spliterator);
private T value;
private boolean valueInReady = false;
private boolean stop = false;
@Override
public boolean hasNext() {
while (true) {
if (valueInReady) return true;
if (stop) return false;
try {
return source.hasNext();
} catch (Exception ex) {
stop = shouldStopTraversing(ex);
handler.accept(ex, this);
}
}
}
@Override
public T next() {
return valueInReady ? dump() : source.next();
}
private T dump() {
T result = value;
valueInReady = false;
value = null;
return result;
}
@Override
public void accept(T value) {
this.value = value;
this.valueInReady = true;
}
}
return new ExceptionallyIterator();
}
static final String BUG_CLASS = "java.util.stream.Streams$StreamBuilderImpl";
public static boolean shouldStopTraversing(Exception ex) {
for (StackTraceElement element : ex.getStackTrace()) {
if (BUG_CLASS.equals(element.getClassName())) {
return true;
}
}
return false;
}
Note: Please see the edit at the end of this post, which fixes a bug in my original answer. I'm leaving my original answer anyway, because it's still useful for many cases and I think it helps solve OP's question, at least with some restrictions.
Your approach with Iterator
goes in the right direction. The solution might be drafted as follows: convert the stream to an iterator, wrap the iterator as you have already done, and then create a stream from the wrapper iterator, except that you should use a Spliterator instead. Here's the code:
private static <T> Stream<T> asNonThrowingStream(
Stream<T> stream,
Supplier<? extends T> valueOnException) {
// Get spliterator from original stream
Spliterator<T> spliterator = stream.spliterator();
// Return new stream from wrapper spliterator
return StreamSupport.stream(
// Extending AbstractSpliterator is enough for our purpose
new Spliterators.AbstractSpliterator<T>(
spliterator.estimateSize(),
spliterator.characteristics()) {
// We only need to implement tryAdvance
@Override
public boolean tryAdvance(Consumer<? super T> action) {
try {
return spliterator.tryAdvance(action);
} catch (RuntimeException e) {
action.accept(valueOnException.get());
return true;
}
}
}, stream.isParallel());
}
We are extending AbstractSpliterator to wrap the spliterator returned by the original stream. We only need to implement the tryAdvance method, which either delegates to the original spliterator's tryAdvance
method, or catches RuntimeException
and invokes the action with the supplied valueOnException
value.
Spliterator
's contract specifies that the return value of tryAdvance
must be true
if the action is consumed, so if a RuntimeException
is catched, it means that the original spliterator has thrown it from within its own tryAdvance
method. Thus, we return true
in this case, meaning that the element was consumed anyway.
The original spliterator's estimate size and characteristics are preserved by passing these values as arguments to the constructor of AbstractSpliterator
.
Finally, we create a new stream from the new spliterator via the StreamSupport.stream
method. The new stream is parallel if the original one was also parallel.
Here's how to use the above method:
public Stream<String> convertToString(Stream<Integer> input) {
return asNonThrowingStream(input.map(String::valueOf), () -> "NaN");
}
As per Holger's comment below, user holi-java has kindly provided a solution that avoids the pitfalls pointed out by Holger.
Here's the code:
<T> Stream<T> exceptionally(Stream<T> source, BiConsumer<Exception, Consumer<? super T>> handler) {
class ExceptionallySpliterator extends AbstractSpliterator<T>
implements Consumer<T> {
private Spliterator<T> source;
private T value;
private long fence;
ExceptionallySpliterator(Spliterator<T> source) {
super(source.estimateSize(), source.characteristics());
this.fence = source.getExactSizeIfKnown();
this.source = source;
}
@Override
public Spliterator<T> trySplit() {
Spliterator<T> it = source.trySplit();
return it == null ? null : new ExceptionallySpliterator(it);
}
@Override
public boolean tryAdvance(Consumer<? super T> action) {
return fence != 0 && consuming(action);
}
private boolean consuming(Consumer<? super T> action) {
Boolean state = tryConsuming(action);
if (state == null) {
return true;
}
if (state) {
action.accept(value);
value = null;
return true;
}
return false;
}
private Boolean tryConsuming(Consumer<? super T> action) {
fence--;
try {
return source.tryAdvance(this);
} catch (Exception ex) {
handler.accept(ex, action);
return null;
}
}
@Override
public void accept(T value) {
this.value = value;
}
}
return stream(new ExceptionallySpliterator(source.spliterator()), source.isParallel()).onClose(source::close);
}
Please refer to the tests if you want to further know about this solution.