Combination of default
methods in Java interface together with Monad pattern enables writing 'interface only' classes. There classes don't have separate implementation classes, just interfaces. Well, of course, technically they have implementation, but that implementation is an inline tiny anonymous class.
I'll demonstrate the idea by writing (incomplete) implementation of Maybe
monad, sometimes called Optional
or Option
. This container is used to represent values which might be present or missing. For example Map.get(key)
can use this container to represent result of the lookup. If value is found then Some
value is returned, otherwise None
(empty container) is returned.
Java 8 has Optional
which implements such a behavior, so we can look at it's API and implement something similar:
public interface Option<T> {
<U> Option<U> map(final Function<T, U> mapper);
<U> Option<U> flatMap(final Function<T, Option<U>> mapper);
Option<T> ifPresent(final Consumer<T> consumer);
Option<T> ifEmpty(final Runnable consumer);
T otherwise(final T replacement);
}
If we try to implement this interface straightforward, we should create a class which will hold value. Then in every method we'll check if that value is null
and if yes, do one action and if not - do other action. Looks like code smell - repeating logic. Let's extract this logic into method, which will accept two functions and will call them depending on the Option
state (i.e. value is present or not):
<U> U map(final Function<T, U> presentMapper, final Supplier<U> emptyMapper);
Now we can represent all remaining methods using this new map()
method:
default <U> Option<U> map(final Function<T, U> mapper) {
return Option.option(map(mapper, () -> null));
}
default <U> Option<U> flatMap(final Function<T, Option<U>> mapper) {
return map(mapper, Option::empty);
}
default Option<T> ifPresent(final Consumer<T> consumer) {
map(v -> {consumer.accept(v); return null;}, () -> null);
return this;
}
default Option<T> ifEmpty(final Runnable consumer) {
map(v -> v, () -> { consumer.run(); return null;} );
return this;
}
default T otherwise(final T replacement) {
return map(v -> v, () -> replacement);
}
Now all we need is two static factory methods - one for creating empty container and one for container with value. Since for all functionality we need only one method, we can use anonymous classes:
static <T> Option<T> option(final T value) {
return (value == null) ? empty() : new Option<T>() {
@Override
public <U> U map(final Function<T, U> presentMapper, final Supplier<U> emptyMapper) {
return presentMapper.apply(value);
}
};
}
static <T> Option<T> empty() {
return new Option<T>() {
@Override
public <U> U map(final Function<T, U> presentMapper, final Supplier<U> emptyMapper) {
return emptyMapper.get();
}
};
}
That's it, all necessary functionality is implemented inside interface.
Interestingly enough is that there is only one conditional operator in the whole code. It is necessary just because we followed Java convention to recognize null
as missing value. This is not strictly necessary and this implementation may hold null
values and still will be able to distinguish present and missing values.
Some other interesting observations:
- Empty instance actually holds no value, there is no even field for it (unlike Java 8
Optional
). We can optimize implementation and return same instance for empty container every time. - There is no explicit instance variable for stored value, it is implicitly stored by Java compiler while creating non-empty instance.
- Lack of branching inside code should help achieve better performance at run time. In this case gain will be negligible, but the technique is general and might be used in cases where gain might be significant.
Top comments (0)