default
Java接口中的方法与Monad模式一起使用,可以编写“仅接口”类。这些类没有单独的实现类,只有接口。好吧,当然,从技术上讲,他们有实现,但是该实现是一个内联的小型匿名类。
我将通过编写(不完整的)Maybe
monad 实现(有时称为Optional
or )来演示该想法Option
。该容器用于表示可能存在或缺失的值。例如Map.get(key)
可以使用该容器表示查找结果。如果找到了Some
值,则返回值,否则返回None
(空容器)。
Java 8具有Optional
实现这种行为的功能,因此我们可以查看它的API并实现类似的功能:
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);
}
如果尝试直接实现此接口,则应创建一个将保留值的类。然后,在每种方法中,我们将检查该值是否是null
,如果是,则执行一个操作,否则执行另一个操作。看起来像代码的味道-重复逻辑。让我们将此逻辑提取到方法中,该方法将接受两个函数并根据Option
状态(即,是否存在值)调用它们:
<U> U map(final Function<T, U> presentMapper, final Supplier<U> emptyMapper);
现在,我们可以使用此新map()
方法表示所有剩余方法:
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);
}
现在,我们需要的是两种静态工厂方法-一种用于创建空容器,另一种用于具有值的容器。由于对于所有功能,我们只需要一种方法,因此可以使用匿名类:
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();
}
};
}
就这样,所有必需的功能都在接口内部实现。
有趣的是,整个代码中只有一个条件运算符。仅因为我们遵循Java约定才能将其识别null
为缺失值,所以这是必要的。这不是严格必需的,并且此实现可以保留null
值,并且仍然能够区分当前值和缺失值。
其他一些有趣的发现:
- 空实例实际上不包含任何值,没有偶数字段(与Java 8不同
Optional
)。我们可以优化实现并每次为空容器返回相同的实例。 - 没有用于存储值的显式实例变量,它由Java编译器在创建非空实例时隐式存储。
- 代码内部缺少分支应该有助于在运行时获得更好的性能。在这种情况下,增益可以忽略不计,但是该技术是通用的,可以在增益可能很大的情况下使用。