流式传输时Rethrow异常

时间:2017-03-23 18:55:28

标签: java functional-programming java-8 java-stream

我无法在“访问”时重新抛出流引发的异常。

例如,如果我有一个引发ExceptionA的流:

Stream<String> stream = Stream.of("dummy").map(d -> {throw new ExceptionA();});

try {
   stream.collect(Collectors.toList());
} catch (ExceptionA e) {}

我想要实现的是从stream2 中创建新的stream而不消费 stream,这会在收集时ExceptionB投放

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包装原始迭代器并返回实际重新生成ExceptionAExceptionB

的新迭代器

不是我想要的,因为stream.iterator()是终端运算符,即它将消耗流,如果流非常大,可能会导致内存问题

3 个答案:

答案 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);
        }
    }
}    

感谢您的评论。干杯!