使用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
对象执行此操作的正确方法是什么?
答案 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
中。
注意:上面的代码可以重构为完全不使用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();
}
}
然后可以这样称呼它:
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)Present
,is(First/Second)OnlyPresent
和are(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()
还请注意:
BiOptional
:if*Present
BiOptionalMapper
:on*Present
on*Present
方法,则如果BiOptionalMapper
被覆盖(例如result
可以处理多个{{1} }电话)BiOptional
的映射器或调用if*Present
不能将result
设置为null
(on*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
的经验法则是不要使用isPresent
和get
。我确实偶尔使用它们。大多数情况下,最好避免使用它们。