我无法在“访问”时重新抛出流引发的异常。
例如,如果我有一个引发ExceptionA
的流:
Stream<String> stream = Stream.of("dummy").map(d -> {throw new ExceptionA();});
try {
stream.collect(Collectors.toList());
} catch (ExceptionA e) {}
我想要实现的是从stream2
中创建新的stream
而不消费 stream
,这会在收集时ExceptionB
投放1} p>
try {
stream2.collect(Collectors.toList());
} catch (ExceptionB e) {}
显然
Iterator<String> newIt = createRethrowingIterator(stream.iterator());
Stream<String> stream2 = StreamSupport.stream(Spliterators.spliteratorUnknownSize(newIt, Spliterator.NONNULL), false)
其中createRethrowingIterator
包装原始迭代器并返回实际重新生成ExceptionA
到ExceptionB
不是我想要的,因为stream.iterator()
是终端运算符,即它将消耗流,如果流非常大,可能会导致内存问题
答案 0 :(得分:2)
使用Spliterator
而不是Iterator
可以更好地解决此任务。它简化了逻辑,因为您只需要通过委派源tryAdvance
方法来实现单个方法tryAdvance
。
通过将方法characteristics()
和estimateSize()
委派给源,它还可以提高性能,因为异常转换功能不会更改它们。您也可以通过委派给源代码来实现trySplit
,从而获得良好的并行支持。您只需将结果与第一个Spliterator
完全相同:
public class Rethrowing<T,E extends Throwable> implements Spliterator<T> {
public static <E extends Throwable, T> Stream<T> translateExceptions(
Stream<T> source, Class<E> catchType,
Function<? super E, ? extends RuntimeException> translator) {
return StreamSupport.stream(new Rethrowing<>(
source.spliterator(), catchType, translator), source.isParallel());
}
private final Spliterator<T> source;
private final Class<E> catchType;
private final Function<? super E, ? extends RuntimeException> translator;
public Rethrowing(Spliterator<T> sp, Class<E> catchType,
Function<? super E, ? extends RuntimeException> translator) {
this.source = sp;
this.catchType = catchType;
this.translator = translator;
}
@Override public boolean tryAdvance(Consumer<? super T> action) {
try { return source.tryAdvance(action); }
catch(Throwable t) {
if(catchType.isInstance(t))
throw translator.apply(catchType.cast(t));
else throw t;
}
}
@Override public int characteristics() {
return source.characteristics();
}
@Override public long estimateSize() {
return source.estimateSize();
}
@Override public Spliterator<T> trySplit() {
Spliterator<T> split = source.trySplit();
return split==null? null: new Rethrowing<>(split, catchType, translator);
}
}
您可以使用此实用程序类,如
class ExceptionA extends IllegalStateException {
public ExceptionA(String s) {
super(s);
}
}
class ExceptionB extends IllegalStateException {
public ExceptionB(Throwable cause) {
super(cause);
}
}
Rethrowing.translateExceptions(
Stream.of("foo", "bar", "baz", "", "extra")
.peek(s -> System.err.println("consuming \""+s+'"'))
.map(s -> { if(s.isEmpty()) throw new ExceptionA("empty"); return s; }),
ExceptionA.class, ExceptionB::new)
.forEach(s -> System.err.println("terminal operation on "+s));
获取
consuming "foo"
terminal operation on foo
consuming "bar"
terminal operation on bar
consuming "baz"
terminal operation on baz
consuming ""
Exception in thread "main" ExceptionB: ExceptionA: empty
…
Caused by: ExceptionA: empty
…
此处,ExceptionB::new
是翻译函数,相当于exA->new ExceptionB(exA)
。
答案 1 :(得分:1)
为什么不将那个将你的ExceptionA
抛出的调用包装成一个映射函数,如果抛出它会立即转换为ExceptionB
,如:
try {
List<T> l = stream.map(o -> wrapped(() -> o.whateverThrowsExceptionA())).collect(toList());
// or do your stream2 operations first, before collecting the list
} catch (ExceptionB b) {
// handle your exception
}
在这种情况下,wrapped
类似于:
<T> T wrapped(Callable<T> o) throws ExceptionB {
try {
return callable.call();
} catch (Exception e) {
throw new ExceptionB(e);
}
}
您甚至可能希望调整包装器以接受自定义ExceptionA
- 捕获功能。
答案 2 :(得分:0)
好吧,我没理解终端操作并不意味着流被完全消耗。谢谢Louis Wasserman澄清这一点。
为了证明这一点,我写了一些单元测试:
import org.junit.Test;
import java.util.Iterator;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* @author Beka Tsotsoria
*/
public class StreamExceptionRethrowingTest {
@Test
public void throwingIteratorMustBeConsumedWhenStreamIsCollected() throws Exception {
ThrowingIterator itToBeConsumed = new ThrowingIterator();
assertThatThrownBy(() -> streamFromIterator(itToBeConsumed)
.collect(Collectors.toList()))
.isInstanceOf(ExceptionA.class);
assertThat(itToBeConsumed.consumed()).isTrue();
}
@Test
public void throwingIteratorMustNotBeConsumedUntilNewStreamIsCollected() throws Exception {
ThrowingIterator itNotToBeConsumed = new ThrowingIterator();
RethrowingIterator rethrowingIterator = new RethrowingIterator(streamFromIterator(itNotToBeConsumed).iterator());
assertThat(itNotToBeConsumed.consumed()).isFalse();
Stream<String> stream2 = streamFromIterator(rethrowingIterator);
assertThat(itNotToBeConsumed.consumed()).isFalse();
assertThatThrownBy(() -> stream2
.collect(Collectors.toList()))
.hasCauseInstanceOf(ExceptionA.class)
.isInstanceOf(ExceptionB.class);
assertThat(itNotToBeConsumed.consumed()).isTrue();
}
@Test
public void streamIteratorMustNotBeConsumedUntilNewStreamIsCollected() throws Exception {
Stream<String> stream = Stream.of("dummy")
.map(d -> {
throw new ExceptionA();
});
Stream<String> stream2 = streamFromIterator(new RethrowingIterator(stream.iterator()));
// No exceptions so far, i.e. stream.iterator() was not consumed
assertThatThrownBy(() -> stream2
.collect(Collectors.toList()))
.hasCauseInstanceOf(ExceptionA.class)
.isInstanceOf(ExceptionB.class);
}
private Stream<String> streamFromIterator(Iterator<String> it) {
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, Spliterator.NONNULL), false);
}
static class ThrowingIterator implements Iterator<String> {
private boolean hasNextCalled;
private boolean nextCalled;
@Override
public boolean hasNext() {
hasNextCalled = true;
throw new ExceptionA();
}
@Override
public String next() {
nextCalled = true;
throw new ExceptionA();
}
public boolean consumed() {
return hasNextCalled || nextCalled;
}
}
static class RethrowingIterator implements Iterator<String> {
private Iterator<String> it;
public RethrowingIterator(Iterator<String> it) {
this.it = it;
}
@Override
public boolean hasNext() {
try {
return it.hasNext();
} catch (ExceptionA e) {
throw new ExceptionB(e);
}
}
@Override
public String next() {
try {
return it.next();
} catch (ExceptionA e) {
throw new ExceptionB(e);
}
}
}
static class ExceptionA extends RuntimeException {
}
static class ExceptionB extends RuntimeException {
public ExceptionB(Throwable cause) {
super(cause);
}
}
}
感谢您的评论。干杯!