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

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对象执行此操作的正确方法是什么?

Lin*_*ica 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();
        }
    }
}
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.

注意:上面的代码可能会被重构为根本不使用OptionalisPresent()检查.而只使用nullfirstsecond并更换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)

但说实话,它仍然有点混乱.


Tom*_*ski 5

免责声明:我的回答是基于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)OnlyPresentare(Both/None)Present方法
  • if(First/Second)Present 方法被重命名为 if(First/Second)OnlyPresent
  • 它有ifNonePresent(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*Present
    • BiOptionalMapper: on*Present
  • 如果任何on*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)

  • 1+用于这个方面的努力,它清楚地表明了意图,我不喜欢的一些事情:`isFirstPresent`等是实现细节,我会把它们变成私有的; 我会完全删除`first/second`方法(因为这不是IMO的实用程序); 我只会留下`areBothPresent`并在需要时简单地执行`!areBothPresent()`.在简短回顾之后我们会有更多的内容,显然我的团队负责人喜欢它并说它移动到`xxx.HolgerUtil`包:)所以......我想我错了,这是我的主要原因在这里开始(读"NICE!") (3认同)

dav*_*xxx 3

为了避免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)