How can I throw CHECKED exceptions from inside Java 8 streams/lambdas?
In other words, I want to make code like this compile:
public List
Summarizing the comments above the advanced solution is to use a special wrapper for unchecked functions with builder like API which provides recovering, rethrowing and suppresing.
Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
.map(Try.>safe(Class::forName)
.handle(System.out::println)
.unsafe())
.collect(toList());
Code below demonstrates it for Consumer, Supplier and Function interfaces. It can be easly expanded. Some public keywords were removed for this example.
Class Try is the endpoint for client code. Safe methods may have unique name for each function type. CheckedConsumer, CheckedSupplier and CheckedFunction are checked analogs of lib functions which can be used independently of Try
CheckedBuilder is the interface for handling exceptions in some checked function. orTry allows execute another same type function if previous was failed. handle provides exception handling including exception type filtering. The order of handlers is important. Reduce methods unsafe and rethrow rethrows last exception in the execution chain. Reduce methods orElse and orElseGet return alternate value like Optional ones if all functions failed. Also there is method suppress. CheckedWrapper is the common implementation of CheckedBuilder.
final class Try {
public static CheckedBuilder, CheckedSupplier, T>
safe(CheckedSupplier supplier) {
return new CheckedWrapper<>(supplier,
(current, next, handler, orResult) -> () -> {
try { return current.get(); } catch (Exception ex) {
handler.accept(ex);
return next.isPresent() ? next.get().get() : orResult.apply(ex);
}
});
}
public static Supplier unsafe(CheckedSupplier supplier) {
return supplier;
}
public static CheckedBuilder, CheckedConsumer, Void>
safe(CheckedConsumer consumer) {
return new CheckedWrapper<>(consumer,
(current, next, handler, orResult) -> t -> {
try { current.accept(t); } catch (Exception ex) {
handler.accept(ex);
if (next.isPresent()) {
next.get().accept(t);
} else {
orResult.apply(ex);
}
}
});
}
public static Consumer unsafe(CheckedConsumer consumer) {
return consumer;
}
public static CheckedBuilder, CheckedFunction, R>
safe(CheckedFunction function) {
return new CheckedWrapper<>(function,
(current, next, handler, orResult) -> t -> {
try { return current.applyUnsafe(t); } catch (Exception ex) {
handler.accept(ex);
return next.isPresent() ? next.get().apply(t) : orResult.apply(ex);
}
});
}
public static Function unsafe(CheckedFunction function) {
return function;
}
@SuppressWarnings ("unchecked")
static T throwAsUnchecked(Throwable exception) throws E {
throw (E) exception;
}
}
@FunctionalInterface interface CheckedConsumer extends Consumer {
void acceptUnsafe(T t) throws Exception;
@Override default void accept(T t) {
try { acceptUnsafe(t); } catch (Exception ex) {
Try.throwAsUnchecked(ex);
}
}
}
@FunctionalInterface interface CheckedFunction extends Function {
R applyUnsafe(T t) throws Exception;
@Override default R apply(T t) {
try { return applyUnsafe(t); } catch (Exception ex) {
return Try.throwAsUnchecked(ex);
}
}
}
@FunctionalInterface interface CheckedSupplier extends Supplier {
T getUnsafe() throws Exception;
@Override default T get() {
try { return getUnsafe(); } catch (Exception ex) {
return Try.throwAsUnchecked(ex);
}
}
}
interface ReduceFunction {
TSafe wrap(TUnsafe current, Optional next,
Consumer handler, Function orResult);
}
interface CheckedBuilder {
CheckedBuilder orTry(TUnsafe next);
CheckedBuilder handle(Consumer handler);
CheckedBuilder handle(
Class exceptionType, Consumer handler);
CheckedBuilder handleLast(Consumer handler);
CheckedBuilder handleLast(
Class exceptionType, Consumer super E> handler);
TSafe unsafe();
TSafe rethrow(Function transformer);
TSafe suppress();
TSafe orElse(R value);
TSafe orElseGet(Supplier valueProvider);
}
final class CheckedWrapper
implements CheckedBuilder {
private final TUnsafe function;
private final ReduceFunction reduceFunction;
private final CheckedWrapper root;
private CheckedWrapper next;
private Consumer handlers = ex -> { };
private Consumer lastHandlers = ex -> { };
CheckedWrapper(TUnsafe function,
ReduceFunction reduceFunction) {
this.function = function;
this.reduceFunction = reduceFunction;
this.root = this;
}
private CheckedWrapper(TUnsafe function,
CheckedWrapper prev) {
this.function = function;
this.reduceFunction = prev.reduceFunction;
this.root = prev.root;
prev.next = this;
}
@Override public CheckedBuilder orTry(TUnsafe next) {
return new CheckedWrapper<>(next, this);
}
@Override public CheckedBuilder handle(
Consumer handler) {
handlers = handlers.andThen(handler);
return this;
}
@Override public CheckedBuilder
handle(Class exceptionType, Consumer handler) {
handlers = handlers.andThen(ex -> {
if (exceptionType.isInstance(ex)) {
handler.accept(exceptionType.cast(ex));
}
});
return this;
}
@Override public CheckedBuilder handleLast(
Consumer handler) {
lastHandlers = lastHandlers.andThen(handler);
return this;
}
@Override public CheckedBuilder
handleLast(Class exceptionType, Consumer super E> handler) {
lastHandlers = lastHandlers.andThen(ex -> {
if (exceptionType.isInstance(ex)) {
handler.accept(exceptionType.cast(ex));
}
});
return this;
}
@Override public TSafe unsafe() {
return root.reduce(ex -> Try.throwAsUnchecked(ex));
}
@Override
public TSafe rethrow(Function transformer) {
return root.reduce(ex -> Try.throwAsUnchecked(transformer.apply(ex)));
}
@Override public TSafe suppress() {
return root.reduce(ex -> null);
}
@Override public TSafe orElse(R value) {
return root.reduce(ex -> value);
}
@Override public TSafe orElseGet(Supplier valueProvider) {
Objects.requireNonNull(valueProvider);
return root.reduce(ex -> valueProvider.get());
}
private TSafe reduce(Function orResult) {
return reduceFunction.wrap(function,
Optional.ofNullable(next).map(p -> p.reduce(orResult)),
this::handle, orResult);
}
private void handle(Throwable ex) {
for (CheckedWrapper current = this;
current != null;
current = current.next) {
current.handlers.accept(ex);
}
lastHandlers.accept(ex);
}
}