-
Notifications
You must be signed in to change notification settings - Fork 8
How it works
Credits where they are due: thanks to Vincentyfication for the design idea
For each functional interface Foo
usable in Stream
s, this package defines an interface
ThrowingFoo
. For instance, here is the base code for the interface ThrowingFunction
:
@FunctionalInterface
public interface ThrowingFunction<T, R>
extends Function<T, R>,
ThrowingFunctionalInterface<ThrowingFunction<T, R>, Function<T, R>>
{
R doApply(T t)
throws Throwable;
@Override
default R apply(T t)
{
try {
return doApply(t);
} catch (Error | RuntimeException e) {
throw e;
} catch (Throwable tooBad) {
throw new ThrownByLambdaException(tooBad);
}
}
}
Note that it extends the base interface; any implementation (class, method reference, lambda...) is therefore also usable as the base type.
All "throwing interfaces" extend this interface, whose code follows; the javadoc should be enough to let you understand what each of these methods do:
public interface ThrowingFunctionalInterface<T extends N, N>
{
/**
* Chain this throwing lambda with another throwing lambda if this one fails
*
* <p>This means that if this lambda fails with an unchecked exception, the
* second one will be tried instead.</p>
*
* <p>Since this method itself returns a throwing lambda, it means you can
* chain more than two lambdas this way: {@code
* l1.orTryWith(l2).orTryWith(l3)} etc.</p>
*
* @param other the other throwing lambda
* @return a new throwing lambda
*/
T orTryWith(T other);
/**
* Chain this throwing lambda with a non throwing lambda fallback
*
* <p>If this throwing lambda fails with a checked exception, the fallback
* (which cannot throw checked exceptions) is invoked instead.</p>
*
* <p>As this method returns a non throwing version, you cannot chain any
* behavior afterwards.</p>
*
* @param fallback the fallback
* @return a non throwing lambda
*/
N fallbackTo(N fallback);
/**
* Change the unchecked exception thrown by this throwing lambda
*
* <p>This allows to change the exception which is thrown if this throwing
* lambda throws a checked exception; as for the default behavior, the
* exception thrown by the lambda is set as the cause of an instance of this
* exception class.</p>
*
* <p>See {@link ThrowablesFactory} for more details.</p>
*
* <p>As this method returns a non throwing version, you cannot chain any
* behavior afterwards.</p>
*
* @param exceptionClass the exception class
* @param <E> the type of this exception class
* @return a non throwing lambda
*/
<E extends RuntimeException> N orThrow(Class<E> exceptionClass);
/**
* Alias to {@link #orThrow(Class)}
*
* <p>This alias is here purely for convenience. It allows you to write:</p>
*
* <pre>
* rethrow(something).as(SomeException.class)
* </pre>
*
* @param exceptionClass the exception class
* @param <E> the type of this exception class
* @return a non throwing lambda
*
* @see #orThrow(Class)
*/
default <E extends RuntimeException> N as(Class<E> exceptionClass)
{
return orThrow(exceptionClass);
}
}
The wrapper methods are defined in a utility class and simply wraps a ThrowingFoo
into a Foo
. For a
Function
, this is defined in class Functions
:
public static <T, R> ThrowingFunction<T, R> wrap(final ThrowingFunction<T, R> f)
{
return f;
}
The primary usage of these wrapping methods are in Stream
s; for instance this:
// ERROR!
.map(Path::toRealPath)
will not work; this, however, does:
// OK, since ThrowingFunction extends Function
.map(Functions.wrap(Path::toRealPath))