Creating a "default constructor" for internal sub-interfaces
Okay, the title can be difficult to understand. I have not found something correct. So, basically I am using Java 8 features to create the Retryable API. I wanted to easily implement these interfaces, so I created a method of(...)
in each implementation of the Retryable interface where we can use lambda expressions instead of manually creating the anonymous class.
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
public interface Retryable<T, R> extends Function<T, R>{
void retrying(Exception e);
void skipping(Exception e);
int trials();
@Override
default R apply(T t) {
int trial = 0;
while (true) {
trial++;
try {
return action(t);
} catch (Exception e) {
if (trial < trials()) {
retrying(e);
} else {
skipping(e);
return null;
}
}
}
}
R action(T input) throws Exception;
interface RunnableRetryable extends Retryable<Void, Void> {
static RunnableRetryable of(Consumer<Exception> retrying, Consumer<Exception> skipping, int trials, CheckedRunnable runnable) {
return new RunnableRetryable() {
@Override
public void retrying(Exception e) {
retrying.accept(e);
}
@Override
public void skipping(Exception e) {
skipping.accept(e);
}
@Override
public int trials() {
return trials;
}
@Override
public Void action(Void v) throws Exception {
runnable.tryRun();
return null;
}
};
}
@FunctionalInterface
interface CheckedRunnable extends Runnable {
void tryRun() throws Exception;
@Override
default void run() {
try {
tryRun();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
interface ConsumerRetryable<T> extends Retryable<T, Void> {
static <T> ConsumerRetryable of(Consumer<Exception> retrying, Consumer<Exception> skipping, int trials, CheckedConsumer<T> consumer) {
return new ConsumerRetryable<T>() {
@Override
public void retrying(Exception e) {
retrying.accept(e);
}
@Override
public void skipping(Exception e) {
skipping.accept(e);
}
@Override
public int trials() {
return trials;
}
@Override
public Void action(T t) throws Exception {
consumer.tryAccept(t);
return null;
}
};
}
@FunctionalInterface
interface CheckedConsumer<T> extends Consumer<T> {
void tryAccept(T t) throws Exception;
@Override
default void accept(T t) {
try {
tryAccept(t);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
interface SupplierRetryable<T> extends Retryable<Void, T> {
static <T> SupplierRetryable of(Consumer<Exception> retrying, Consumer<Exception> skipping, int trials, CheckedSupplier<T> supplier) {
return new SupplierRetryable<T>() {
@Override
public void retrying(Exception e) {
retrying.accept(e);
}
@Override
public void skipping(Exception e) {
skipping.accept(e);
}
@Override
public int trials() {
return trials;
}
@Override
public T action(Void v) throws Exception {
return supplier.tryGet();
}
};
}
@FunctionalInterface
interface CheckedSupplier<T> extends Supplier<T> {
T tryGet() throws Exception;
@Override
default T get() {
try {
return tryGet();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
interface FunctionRetryable<T, R> extends Retryable<T, R> {
static <T, R> FunctionRetryable of(Consumer<Exception> retrying, Consumer<Exception> skipping, int trials, CheckedFunction<T, R> function) {
return new FunctionRetryable<T, R>() {
@Override
public void retrying(Exception e) {
retrying.accept(e);
}
@Override
public void skipping(Exception e) {
skipping.accept(e);
}
@Override
public int trials() {
return trials;
}
@Override
public R action(T t) throws Exception {
return function.tryApply(t);
}
};
}
@FunctionalInterface
interface CheckedFunction<T, R> extends Function<T, R> {
R tryApply(T t) throws Exception;
@Override
default R apply(T t) {
try {
return tryApply(t);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
}
But as you can see, of(...)
there is a lot of duplicate code in each method . I could create a kind of "constructor" (that's not the right word, because interfaces can't have a constructor) in the Retryable interface, but I don't know how. Anyone have an idea?
source to share
The main problem is your API explosion. All of these nested interfaces extending Retryable
don't add any functionality, but require the user of this code to deal with them as soon as they become part of the API. They also cause code duplication, since each of these redundant interfaces requires its own implementation, whereas all implementations basically do the same.
After removing these deprecated types, you can simply perform the operations as delegation:
public interface Retryable<T, R> extends Function<T, R>{
void retrying(Exception e);
void skipping(Exception e);
int trials();
@Override default R apply(T t) {
try { return action(t); }
catch(Exception e) {
for(int trial = 1; trial < trials(); trial++) {
retrying(e);
try { return action(t); } catch (Exception next) { e=next; }
}
skipping(e);
return null;
}
}
R action(T input) throws Exception;
public static Retryable<Void, Void> of(Consumer<Exception> retrying,
Consumer<Exception> skipping, int trials, CheckedRunnable runnable) {
return of(retrying, skipping, trials, x -> { runnable.tryRun(); return null; });
}
@FunctionalInterface interface CheckedRunnable extends Runnable {
void tryRun() throws Exception;
@Override default void run() {
try { tryRun(); } catch (Exception e) { throw new RuntimeException(e); }
}
}
public static <T> Retryable<T, Void> of(Consumer<Exception> retrying,
Consumer<Exception> skipping, int trials, CheckedConsumer<T> consumer) {
return of(retrying, skipping, trials,
value -> { consumer.tryAccept(value); return null; });
}
@FunctionalInterface interface CheckedConsumer<T> extends Consumer<T> {
void tryAccept(T t) throws Exception;
@Override default void accept(T t) {
try { tryAccept(t); } catch (Exception e) { throw new RuntimeException(e); }
}
}
public static <T> Retryable<Void, T> of(Consumer<Exception> retrying,
Consumer<Exception> skipping, int trials, CheckedSupplier<T> supplier) {
return of(retrying, skipping, trials, voidArg -> { return supplier.tryGet(); });
}
@FunctionalInterface interface CheckedSupplier<T> extends Supplier<T> {
T tryGet() throws Exception;
@Override default T get() {
try { return tryGet(); }
catch (Exception e) { throw new RuntimeException(e); }
}
}
public static <T, R> Retryable<T, R> of(Consumer<Exception> retrying,
Consumer<Exception> skipping, int trials, CheckedFunction<T, R> function) {
return new Retryable<T, R>() {
@Override public void retrying(Exception e) { retrying.accept(e); }
@Override public void skipping(Exception e) { skipping.accept(e); }
@Override public int trials() { return trials; }
@Override public R action(T t) throws Exception {
return function.tryApply(t);
}
};
}
@FunctionalInterface interface CheckedFunction<T, R> extends Function<T, R> {
R tryApply(T t) throws Exception;
@Override default R apply(T t) {
try { return tryApply(t); }
catch (Exception e) { throw new RuntimeException(e); }
}
}
}
Only one implementation class is required to deal with the argument and the return value, others can simply delegate it using the adapter function, doing either by dropping the argument, or by returning null
, or both.
In most cases, the form of a lambda expression is appropriate for choosing the correct method, eg.
Retryable<Void,Void> r = Retryable.of(e -> {}, e -> {}, 3, () -> {});
Retryable<Void,String> s = Retryable.of(e -> {}, e -> {}, 3, () -> "foo");
Retryable<Integer,Integer> f = Retryable.of(e -> {}, e -> {}, 3, i -> i/0);
but sometimes a little hint is needed:
// braces required to disambiguate between Function and Consumer
Retryable<String,Void> c = Retryable.of(e->{}, e ->{}, 3,
str -> { System.out.println(str); });
source to share
It looks like you can influence some of these in a (perhaps in a package-private) abstract class:
abstract class AbstractRetryable<T, R> implements Retryable<T, R> {
private final Consumer<Exception> retrying;
private final Consumer<Exception> skipping;
private final int trials;
AbstractRetryable(Consumer<Exception> retrying,
Consumer<Exception> skipping,
int trials) {
this.retrying = Objects.requireNonNull(retrying, "retrying");
this.skipping = Objects.requireNonNull(skipping, "skipping");
this.trials = trials;
}
@Override
public void retrying(Exception x) {
retrying.accept(x);
}
@Override
public void skipping(Exception x) {
skipping.accept(x);
}
@Override
public int trials() {
return trials;
}
}
The only problem is that you are using subinterfaces, so you cannot create an anonymous class that extends the abstract class and implements a helper interface.
Then you could write additional (again, possibly batch) subclasses:
final class RunnableRetryableImpl
extends AbstractRetryable<Void, Void>
implements RunnableRetryable {
private final CheckedRunnable runnable;
RunnableRetryableImpl(Consumer<Exception> retrying,
Consumer<Exception> skipping,
int trials,
CheckedRunnable runnable) {
super(retrying, skipping, trials);
this.runnable = Objects.requireNonNull(runnable, "runnable");
}
@Override
public Void apply(Void ignored) {
try {
runnable.tryRun();
} catch (Exception x) {
// BTW I would consider doing this.
if (x instanceof RuntimeException)
throw (RuntimeException) x;
// I would also probably write a class like:
// class RethrownException extends RuntimeException {
// RethrownException(Exception cause) {
// super(cause);
// }
// }
// This way the caller can catch a specific type if
// they want to.
// (See e.g. java.io.UncheckedIOException)
throw new RuntimeException(x);
}
return null;
}
}
Or, you can reduce the number of lines with local classes:
static RunnableRetryable of(Consumer<Exception> retrying,
Consumer<Exception> skipping,
int trials,
CheckedRunnable runnable) {
Objects.requireNonNull(runnable, "runnable");
final class RunnableRetryableImpl
extends AbstractRetryable<Void, Void>
implements RunnableRetryable {
RunnableRetryable() {
// Avoid explicitly declaring parameters
// and passing arguments.
super(retrying, skipping, trials);
}
@Override
public Void apply(Void ignored) {
try {
runnable.tryRun();
} catch (Exception x) {
if (x instanceof RuntimeException)
throw (RuntimeException) x;
throw new RuntimeException(x);
}
return null;
}
}
return new RunnableRetryableImpl();
}
Personally, I think I'll just write package-private implementations instead of local classes, but this of course requires a fair amount of boiler room code.
Also, as a side note, when you write factories that return anonymous classes, you must use requireNonNull
inside the method itself (as in my example of
). It is so that if null
passed to a method, the method throws the NPE instead of eg. some call on retrying
or skipping
drop NPE after a while.
source to share