Nul*_*ter 10 java optional java-8
在使用Java 8 Optionals时,我经常遇到以下情况.我有两个Optional对象,然后我想根据ifPresent这些Optionals 的值()调用不同的方法.
这是一个例子:
void example(Optional<String> o1, Optional<String> o2) throws Exception {
if (o1.isPresent() && o2.isPresent()) {
handler1(o1.get(), o2.get());
} else if (o1.isPresent()) {
handler2(o1.get());
} else if (o2.isPresent()) {
handler3(o2.get());
} else {
throw new Exception();
}
}
Run Code Online (Sandbox Code Playgroud)
但是,这一系列if-else语句似乎不是一种正确的使用方式Optional(毕竟,它们被添加,以便您可以避免if-else在代码中的任何地方编写这些检查).
使用Optional对象执行此操作的正确方法是什么?
你说你经常使用这种结构,所以我建议你介绍一个帮手class:
final class BiOptionalHelper<F, S> {
private final Optional<F> first;
private final Optional<S> second;
public BiOptionalHelper(Optional<F> first, Optional<S> second){
this.first = first;
this.second = second;
}
public BiOptionalHelper<F, S> ifFirstPresent(Consumer<? super F> ifPresent){
if (!second.isPresent()) {
first.ifPresent(ifPresent);
}
return this;
}
public BiOptionalHelper<F, S> ifSecondPresent(Consumer<? super S> ifPresent){
if (!first.isPresent()) {
second.ifPresent(ifPresent);
}
return this;
}
public BiOptionalHelper<F, S> ifBothPresent(BiConsumer<? super F, ? super S> ifPresent){
if(first.isPresent() && second.isPresent()){
ifPresent.accept(first.get(), second.get());
}
return this;
}
public <T extends Throwable> void orElseThrow(Supplier<? extends T> exProvider) throws T{
if(!first.isPresent() && !second.isPresent()){
throw exProvider.get();
}
}
}
Run Code Online (Sandbox Code Playgroud)
然后可以以这样的方式使用:
new BiOptionalHelper<>(o1, o2)
.ifBothPresent(this::handler1)
.ifFirstPresent(this::handler2)
.ifSecondPresent(this::handler3)
.orElseThrow(Exception::new);
Run Code Online (Sandbox Code Playgroud)
但是,这只是将您的问题转移到一个单独的问题class.
注意:上面的代码可能会被重构为根本不使用Optional和isPresent()检查.而只使用null了first和second并更换isPresent()有null-checks.
因为它通常是一个糟糕的设计,存储Optional在字段中或首先接受它们作为参数.正如JB Nizet在对该问题的评论中已经指出的那样.
将该逻辑移动到公共辅助方法的另一种方法:
public static <F, S, T extends Throwable> void handle(Optional<F> first, Optional<S> second,
BiConsumer<F, S> bothPresent, Consumer<F> firstPresent,
Consumer<S> secondPresent, Supplier<T> provider) throws T{
if(first.isPresent() && second.isPresent()){
bothPresent.accept(first.get(), second.get());
} else if(first.isPresent()){
firstPresent.accept(first.get());
} else if(second.isPresent()){
secondPresent.accept(second.get());
} else{
throw provider.get();
}
}
Run Code Online (Sandbox Code Playgroud)
然后可以像这样调用:
handle(o1, o2, this::handler1, this::handler2, this::handler3, Exception::new);
Run Code Online (Sandbox Code Playgroud)
但说实话,它仍然有点混乱.
免责声明:我的回答是基于Lino的答案 - 这个答案的第一部分(BiOptional<T, U>)是Lino的BiOptionalHelper修改版本,而第二部分(BiOptionalMapper<T, U, R>)是我扩展这个漂亮模式的想法.
我非常喜欢Lino的答案.但是,我认为不BiOptionalHelper应该调用它,它应该被简单地调用BiOptional,前提是:
Optional<T> first()和Optional<T> second()方法is(First/Second)Present,is(First/Second)OnlyPresent和are(Both/None)Present方法if(First/Second)Present 方法被重命名为 if(First/Second)OnlyPresentifNonePresent(Runnable action)方法orElseThrow 方法重命名为 ifNonePresentThrow最后(这是我答案的完全原创部分),我意识到这种模式不仅可以支持"处理"(in BiOptional),还可以支持"映射"(BiOptionalMapper通过获得BiOptional.mapper()),如下所示:
BiOptional<String, Integer> biOptional = BiOptional.from(o1, o2);
// handler version
biOptional
.ifBothPresent(this::handleBoth)
.ifFirstOnlyPresent(this::handleFirst)
.ifSecondOnlyPresent(this::handleSecond)
.ifNonePresent(this::performAction);
// mapper version
String result = biOptional.<String>mapper()
.onBothPresent(this::mapBoth)
.onFirstOnlyPresent(this::mapFirst)
.onSecondOnlyPresent(this::mapSecond)
.onNonePresent("default")
.result();
Optional<String> optionalResult = biOptional.<String>mapper()
.onBothPresent(this::mapBoth)
.onNonePresentThrow(IllegalStateException::new)
.optionalResult();
Run Code Online (Sandbox Code Playgroud)
请注意,可以:
on*Present映射方法,然后调用R result()(如果result不存在则抛出),或者Optional<R> optionalResult()还要注意:
BiOptional: if*PresentBiOptionalMapper: on*Presenton*Present方法被调用两次,BiOptionalMapper将抛出如果result要被覆盖(不像BiOptional,它可以处理多个if*Present调用)result不能被设置到null由提供给映射器on*Present或通过调用onNonePresent(R)(Optional<...>应作为结果类型R代替)这是两个类的源代码:
final class BiOptional<T, U> {
@Nullable
private final T first;
@Nullable
private final U second;
public BiOptional(T first, U second) {
this.first = first;
this.second = second;
}
public static <T, U> BiOptional<T, U> from(Optional<T> first, Optional<U> second) {
return new BiOptional<>(first.orElse(null), second.orElse(null));
}
public Optional<T> first() {
return Optional.ofNullable(first);
}
public Optional<U> second() {
return Optional.ofNullable(second);
}
public boolean isFirstPresent() {
return first != null;
}
public boolean isSecondPresent() {
return second != null;
}
public boolean isFirstOnlyPresent() {
return isFirstPresent() && !isSecondPresent();
}
public boolean isSecondOnlyPresent() {
return !isFirstPresent() && isSecondPresent();
}
public boolean areBothPresent() {
return isFirstPresent() && isSecondPresent();
}
public boolean areNonePresent() {
return !isFirstPresent() && !isSecondPresent();
}
public BiOptional<T, U> ifFirstOnlyPresent(Consumer<? super T> ifFirstOnlyPresent) {
if (isFirstOnlyPresent()) {
ifFirstOnlyPresent.accept(first);
}
return this;
}
public BiOptional<T, U> ifSecondOnlyPresent(Consumer<? super U> ifSecondOnlyPresent) {
if (isSecondOnlyPresent()) {
ifSecondOnlyPresent.accept(second);
}
return this;
}
public BiOptional<T, U> ifBothPresent(BiConsumer<? super T, ? super U> ifBothPresent) {
if (areBothPresent()) {
ifBothPresent.accept(first, second);
}
return this;
}
public BiOptional<T, U> ifNonePresent(Runnable ifNonePresent) {
if (areNonePresent()) {
ifNonePresent.run();
}
return this;
}
public <X extends Throwable> void ifNonePresentThrow(Supplier<? extends X> throwableProvider) throws X {
if (areNonePresent()) {
throw throwableProvider.get();
}
}
public <R> BiOptionalMapper<T, U, R> mapper() {
return new BiOptionalMapper<>(this);
}
}
Run Code Online (Sandbox Code Playgroud)
和:
final class BiOptionalMapper<T, U, R> {
private final BiOptional<T, U> biOptional;
private R result = null;
BiOptionalMapper(BiOptional<T, U> biOptional) {
this.biOptional = biOptional;
}
public BiOptionalMapper<T, U, R> onFirstOnlyPresent(Function<? super T, ? extends R> firstMapper) {
if (biOptional.isFirstOnlyPresent()) {
setResult(firstMapper.apply(biOptional.first().get()));
}
return this;
}
public BiOptionalMapper<T, U, R> onSecondOnlyPresent(Function<? super U, ? extends R> secondMapper) {
if (biOptional.isSecondOnlyPresent()) {
setResult(secondMapper.apply(biOptional.second().get()));
}
return this;
}
public BiOptionalMapper<T, U, R> onBothPresent(BiFunction<? super T, ? super U, ? extends R> bothMapper) {
if (biOptional.areBothPresent()) {
setResult(bothMapper.apply(biOptional.first().get(), biOptional.second().get()));
}
return this;
}
public BiOptionalMapper<T, U, R> onNonePresent(Supplier<? extends R> supplier) {
if (biOptional.areNonePresent()) {
setResult(supplier.get());
}
return this;
}
public BiOptionalMapper<T, U, R> onNonePresent(R other) {
if (biOptional.areNonePresent()) {
setResult(other);
}
return this;
}
public <X extends Throwable> BiOptionalMapper<T, U, R> onNonePresentThrow(Supplier<? extends X> throwableProvider) throws X {
biOptional.ifNonePresentThrow(throwableProvider);
return this;
}
public R result() {
if (result == null) {
throw new IllegalStateException("Result absent");
}
return result;
}
public Optional<R> optionalResult() {
return Optional.ofNullable(result);
}
private void setResult(R result) {
if (result == null) {
throw new IllegalArgumentException("Null obtained from a mapper");
}
if (this.result != null) {
throw new IllegalStateException("Result already present: " + this.result);
}
this.result = result;
}
}
Run Code Online (Sandbox Code Playgroud)
为了避免if在这里使用 or 语句,if (Optional.isPresent())您应该有一种通用的方法来处理这些Optional值,但事实并非如此,因为根据它们的内容,您可以使用功能接口Consumer<String>或调用函数BiConsumer<String, String>。
作为提示,您可以分解第二部分,但这并不是更具可读性或更好的方法:
if (o1.isPresent() && o2.isPresent()) {
handler1(o1.get(), o2.get());
} else {
Map<Optional<String>, Consumer<String>> map = new HashMap<>();
map.put(o1, this::handler2);
map.put(o2, this::handler3);
Optional<String> opt = Stream.of(o1, o2)
.filter(Optional::isPresent)
.findFirst()
.orElseThrow(Exception::new);
map.get(opt)
.accept(opt.get());
}
Run Code Online (Sandbox Code Playgroud)
如果您有更多的Optional事情需要以这种方式处理,这样可能会更有意义,但仍然需要编写大量代码。
一个更具可读性的替代方案可能是引入一个Rule类,该类存储触发该事件所需的信息(如果需要):
public Rule(BiPredicate<Optional<String>, Optional<String>> ruleFunction, Runnable runnableIfApplied) {
this.ruleFunction = ruleFunction;
this.runnable = runnableIfApplied;
}
Run Code Online (Sandbox Code Playgroud)
表示BiPredicate<Optional<String>, Optional<String>>匹配函数, 是Runnable匹配发生时执行的方法。
您可以在方法中移动规则执行逻辑Rule static。
这个想法是尽可能清楚地说明客户端的规则规范,例如:
void example(Optional<String> o1, Optional<String> o2, Optional<String> o3) throws Exception {
Rule.executeFirstMatchOrFail(o1, o2,
new Rule((opt1, opt2) -> opt1.isPresent() && opt2.isPresent(), () -> handler1(o1.get(), o2.get())),
new Rule((opt1, opt2) -> opt1.isPresent(), () -> handler2(o1.get())),
new Rule((opt1, opt2) -> opt2.isPresent(), () -> handler3(o2.get())));
}
Run Code Online (Sandbox Code Playgroud)
Rule可能看起来像:
public class Rule {
static void executeFirstMatchOrFail(Optional<String> o1, Optional<String> o2, Rule... rules) throws Exception {
for (Rule rule : rules) {
if (rule.apply(o1, o2)) {
return;
}
}
throw new Exception();
}
private Runnable runnable;
private BiPredicate<Optional<String>, Optional<String>> ruleFunction;
public Rule(BiPredicate<Optional<String>, Optional<String>> ruleFunction, Runnable runnableIfApplied) {
this.ruleFunction = ruleFunction;
this.runnable = runnableIfApplied;
}
public boolean apply(Optional<String> o1, Optional<String> o2) {
if (ruleFunction.test(o1,o2)) {
runnable.run();
return true;
}
return false;
}
}
Run Code Online (Sandbox Code Playgroud)