根据两个Optional的值调用不同的方法

时间:2018-08-14 18:14:49

标签: java java-8 optional

使用Java 8 Optionals时,我经常遇到以下情况。我有两个Optional对象,然后我想基于那些Optionals的值(ifPresent)调用不同的方法。

这里是一个例子:

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();
    }
}

但是,这条if-else语句链似乎并不是处理Optional的正确方法(毕竟,已添加它们,因此您可以避免编写这些if-else检查代码中的任何地方)。

使用Optional对象执行此操作的正确方法是什么?

4 个答案:

答案 0 :(得分:6)

您说您经常使用这种结构,所以我建议介绍一个助手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();
        }
    }
}

然后可以这样使用:

new BiOptionalHelper<>(o1, o2)
    .ifBothPresent(this::handler1)
    .ifFirstPresent(this::handler2)
    .ifSecondPresent(this::handler3)
    .orElseThrow(Exception::new);

尽管如此,这只会将您的问题移到单独的class中。

注意:上面的代码可以重构为完全不使用OptionalisPresent()检查。只需对nullfirst使用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();
    }
}

然后可以这样称呼它:

handle(o1, o2, this::handler1, this::handler2, this::handler3, Exception::new);

但是,老实说还是有点混乱。

答案 1 :(得分:5)

免责声明:我的答案基于Lino's answer-此答案的第一部分(BiOptional<T, U>)是Lino's BiOptionalHelper的修改版本,第二部分(BiOptionalMapper<T, U, R>部分是我扩展这种漂亮模式的想法。

我非常喜欢Lino's answer。但是,我认为它不应该被称为BiOptionalHelper,而不是称为BiOptional,只要:

  • 它获得Optional<T> first()Optional<T> second()方法
  • 它获得is(First/Second)Presentis(First/Second)OnlyPresentare(Both/None)Present方法
  • if(First/Second)Present方法重命名为if(First/Second)OnlyPresent
  • 它获得ifNonePresent(Runnable action)方法
  • orElseThrow方法重命名为ifNonePresentThrow

最后(这是我回答的全部原始内容),我意识到这种模式不仅可以支持“处理”(在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();

请注意,一个可以:

  • 调用所有on*Present映射方法,然后调用R result()(如果不存在result则抛出),或者
  • 仅呼叫其中一些,然后呼叫Optional<R> optionalResult()

还请注意:

  • 为了避免“处理”和“映射”之间的混淆,命名约定如下:
    • BiOptionalif*Present
    • BiOptionalMapperon*Present
  • 如果两次调用任何on*Present方法,则如果BiOptionalMapper被覆盖(例如result可以处理多个{{1} }电话)
  • 提供给BiOptional的映射器或调用if*Present不能将
  • result设置为nullon*Present用作结果类型onNonePresent(R)

这是两个类的源代码:

Optional<...>

和:

R

答案 2 :(得分:3)

为避免出现if语句或此处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());
}

如果您有更多Optional这样处理,可能会更有意义,但是仍然要编写很多代码。


一种更具可读性的替代方法是引入一个Rule类,该类存储必要时触发该信息所需的信息:

public Rule(BiPredicate<Optional<String>, Optional<String>> ruleFunction, Runnable runnableIfApplied) {
    this.ruleFunction = ruleFunction;
    this.runnable = runnableIfApplied;
}

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())));
}

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;
    }

}

答案 3 :(得分:3)

它并不能真正回答您的问题,但是自Java 9起,我希望遵循以下几点:

    o1.ifPresentOrElse(s1 -> {
        o2.ifPresentOrElse(s2 -> {
               handler1(s1, s2);
        }, () -> {
               handler2(s1);
        });
    }, () -> {
        o2.ifPresentOrElse(s2 -> {
               handler3(s2);
        }, () -> {
               throw new IllegalArgumentException("Neither was present");
        });
    });

关于Optional的经验法则是不要使用isPresentget。我确实偶尔使用它们。大多数情况下,最好避免使用它们。