如果所有三个都存在,如何使用 Java 8 Optionals 执行操作?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/48446622/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to use Java 8 Optionals, performing an action if all three are present?
提问by hughjdavey
I have some (simplified) code that uses Java Optionals:
我有一些使用 Java Optionals 的(简化的)代码:
Optional<User> maybeTarget = userRepository.findById(id1);
Optional<String> maybeSourceName = userRepository.findById(id2).map(User::getName);
Optional<String> maybeEventName = eventRepository.findById(id3).map(Event::getName);
maybeTarget.ifPresent(target -> {
maybeSourceName.ifPresent(sourceName -> {
maybeEventName.ifPresent(eventName -> {
sendInvite(target.getEmail(), String.format("Hi %s, $s has invited you to $s", target.getName(), sourceName, meetingName));
}
}
}
Needless to say, this looks and feels bad. But I can't think of another way to do this in a less-nested and more readable way. I considered streaming the 3 Optionals, but discarded the idea as doing a .filter(Optional::isPresent)
then a .map(Optional::get)
feels even worse.
不用说,这看起来和感觉都很糟糕。但是我想不出另一种方法来以更少嵌套和更易读的方式来做到这一点。我认为流3个选配,但丢弃的想法,做了.filter(Optional::isPresent)
那么.map(Optional::get)
更糟糕的感觉。
So is there a better, more 'Java 8' or 'Optional-literate' way of dealing with this situation (essentially multiple Optionals all needed to compute a final operation)?
那么是否有更好的、更多的“Java 8”或“Optional-literate”的方式来处理这种情况(基本上计算最终操作都需要多个 Optional)?
采纳答案by Sharon Ben Asher
I think to stream the three Optional
s is an overkill, why not the simple
我认为流式传输三个Optional
s 是一种矫枉过正,为什么不简单
if (maybeTarget.isPresent() && maybeSourceName.isPresent() && maybeEventName.isPresent()) {
...
}
In my eyes, this states the conditional logic more clearly compared to the use of the stream API.
在我看来,与使用流 API 相比,这更清楚地说明了条件逻辑。
回答by Jorn Vernee
Using a helper function, things at least become un-nested a little:
使用辅助函数,事情至少变得不那么嵌套了:
@FunctionalInterface
interface TriConsumer<T, U, S> {
void accept(T t, U u, S s);
}
public static <T, U, S> void allOf(Optional<T> o1, Optional<U> o2, Optional<S> o3,
TriConsumer<T, U, S> consumer) {
o1.ifPresent(t -> o2.ifPresent(u -> o3.ifPresent(s -> consumer.accept(t, u, s))));
}
allOf(maybeTarget, maybeSourceName, maybeEventName,
(target, sourceName, eventName) -> {
/// ...
});
The obvious downside being that you'd need a separate helper function overload for every different number of Optional
s
明显的缺点是您需要为每个不同数量的Optional
s单独重载一个辅助函数
回答by Stuart Marks
Since the original code is being executed for its side effects (sending an email), and not extracting or generating a value, the nested ifPresent
calls seem appropriate. The original code doesn't seem too bad, and indeed it seems rather better than some of the answers that have been proposed. However, the statement lambdas and the local variables of type Optional
do seem to add a fair amount of clutter.
由于原始代码正在执行其副作用(发送电子邮件),而不是提取或生成值,因此嵌套ifPresent
调用似乎是合适的。原始代码看起来并不太糟糕,实际上它似乎比提出的一些答案要好得多。然而,语句 lambdas 和类型的局部变量Optional
似乎确实增加了相当多的混乱。
First, I'll take the liberty of modifying the original code by wrapping it in a method, giving the parameters nice names, and making up some type names. I have no idea if the actual code is like this, but this shouldn't really be surprising to anyone.
首先,我将通过将原始代码包装在一个方法中、为参数提供好听的名称并组成一些类型名称来自由地修改原始代码。我不知道实际的代码是否是这样的,但这对任何人来说都不应该感到惊讶。
// original version, slightly modified
void inviteById(UserId targetId, UserId sourceId, EventId eventId) {
Optional<User> maybeTarget = userRepository.findById(targetId);
Optional<String> maybeSourceName = userRepository.findById(sourceId).map(User::getName);
Optional<String> maybeEventName = eventRepository.findById(eventId).map(Event::getName);
maybeTarget.ifPresent(target -> {
maybeSourceName.ifPresent(sourceName -> {
maybeEventName.ifPresent(eventName -> {
sendInvite(target.getEmail(), String.format("Hi %s, %s has invited you to %s",
target.getName(), sourceName, eventName));
});
});
});
}
I played around with different refactorings, and I found that extracting the inner statement lambda into its own method makes the most sense to me. Given source and target users and an event -- no Optional stuff -- it sends mail about it. This is the computation that needs to be performed after all the optional stuff has been dealt with. I've also moved the data extraction (email, name) in here instead of mixing it with the Optional processing in the outer layer. Again, this makes sense to me: send mail from sourceto targetabout event.
我尝试了不同的重构,我发现将内部语句 lambda 提取到它自己的方法中对我来说最有意义。给定源用户和目标用户以及一个事件——没有可选的东西——它会发送关于它的邮件。这是在处理完所有可选内容后需要执行的计算。我还将数据提取(电子邮件、姓名)移到此处,而不是将其与外层中的 Optional 处理混合。同样,这对我来说很有意义:将关于event 的邮件从源发送到目标。
void setupInvite(User target, User source, Event event) {
sendInvite(target.getEmail(), String.format("Hi %s, %s has invited you to %s",
target.getName(), source.getName(), event.getName()));
}
Now, let's deal with the optional stuff. As I said above, ifPresent
is the way to go here, since we want to do something with side effects. It also provides a way to "extract" the value from an Optional and bind it to a name, but only within the context of a lambda expression. Since we want to do this for three different Optionals, nesting is called for. Nesting allows names from outer lambdas to be captured by inner lambdas. This lets us bind names to values extracted from the Optionals -- but only if they're present. This can't really be done with a linear chain, since some intermediate data structure like a tuple would be necessary to build up the partial results.
现在,让我们处理可选的东西。正如我上面所说的,这ifPresent
是要走的路,因为我们想做一些有副作用的事情。它还提供了一种从 Optional 中“提取”值并将其绑定到名称的方法,但仅限于 lambda 表达式的上下文中。由于我们要为三个不同的 Optional 执行此操作,因此需要嵌套。嵌套允许内部 lambda 捕获来自外部 lambda 的名称。这让我们可以将名称绑定到从 Optionals 中提取的值——但前提是它们存在。这不能用线性链来完成,因为需要像元组这样的一些中间数据结构来构建部分结果。
Finally, in the innermost lambda, we call the helper method defined above.
最后,在最里面的 lambda 中,我们调用上面定义的辅助方法。
void inviteById(UserId targetId, UserId sourceID, EventId eventId) {
userRepository.findById(targetId).ifPresent(
target -> userRepository.findById(sourceID).ifPresent(
source -> eventRepository.findById(eventId).ifPresent(
event -> setupInvite(target, source, event))));
}
Note that I've inlined the Optionals instead of holding them in local variables. This reveals the nesting structure a bit better. It also provides for "short-circuiting" of the operation if one of the lookups doesn't find anything, since ifPresent
simply does nothing on an empty Optional.
请注意,我已经内联了 Optionals 而不是将它们保存在局部变量中。这更好地揭示了嵌套结构。如果其中一个查找没有找到任何内容,它还提供操作的“短路”,因为ifPresent
在空的 Optional 上根本不做任何事情。
It's still a bit dense to my eye, though. I think the reason is that this code still depends on some external repositories on which to do the lookups. It's a bit uncomfortable to have this mixed together with the Optional processing. A possibility is simply to extract the lookups into their own methods findUser
and findEvent
. These are pretty obvious so I won't write them out. But if this were done, the result would be:
不过,我的眼睛仍然有点浓。我认为原因是这段代码仍然依赖于一些进行查找的外部存储库。将其与 Optional 处理混合在一起有点不舒服。一种可能性是简单地将查找提取到它们自己的方法findUser
和findEvent
. 这些很明显,所以我不会写出来。但如果这样做了,结果将是:
void inviteById(UserId targetId, UserId sourceID, EventId eventId) {
findUser(targetId).ifPresent(
target -> findUser(sourceID).ifPresent(
source -> findEvent(eventId).ifPresent(
event -> setupInvite(target, source, event))));
}
Fundamentally, this isn't that different from the original code. It's subjective, but I think I prefer this to the original code. It has the same, fairly simple structure, although nested instead of the typical linear chain of Optional processing. What's different is that the lookups are done conditionally within Optional processing, instead of being done up front, stored in local variables, and then doing only conditional extraction of Optional values. Also, I've separated out data manipulation (extraction of email and name, sending of message) into a separate method. This avoids mixing data manipulation with Optional processing, which I think tends to confuse things if we're dealing with multiple Optional instances.
从根本上说,这与原始代码没有什么不同。这是主观的,但我想我更喜欢这个而不是原始代码。它具有相同的、相当简单的结构,尽管是嵌套的,而不是典型的 Optional 处理线性链。不同的是,查找是在 Optional 处理中有条件地完成的,而不是预先完成,存储在局部变量中,然后只对 Optional 值进行有条件的提取。此外,我已将数据操作(提取电子邮件和姓名、发送消息)分离到一个单独的方法中。这避免了将数据操作与 Optional 处理混合在一起,我认为如果我们处理多个 Optional 实例,这往往会使事情变得混乱。
回答by pvpkiran
How about something like this
这样的事情怎么样
if(Stream.of(maybeTarget, maybeSourceName,
maybeEventName).allMatch(Optional::isPresent))
{
sendinvite(....)// do get on all optionals.
}
Having said that. If your logic to find in database is only to send mail, then if maybeTarget.ifPresent()
is false, then there is no point to fetch the other two values, ain't it?. I am afraid, this kinda logic can be achieved only through traditional if else statements.
话说回来。如果您在数据库中查找的逻辑只是发送邮件,那么如果maybeTarget.ifPresent()
为假,那么获取其他两个值就没有意义了,不是吗?。恐怕,这种逻辑只能通过传统的 if else 语句来实现。
回答by Federico Peralta Schaffner
I think you should consider taking another approach.
我认为你应该考虑采取另一种方法。
I'd start by not issuing the three calls to the DB at the beginning. Instead, I'd issue the 1st query and only if the result is present, I'd issue the 2nd one. I'd then apply the same rationale with regard to the 3rd query and finally, if the last result is also present, I'd send the invite. This would avoid unnecessary calls to the DB when either one of the first two results is not present.
我首先不会在开始时向数据库发出三个调用。相反,我会发出第一个查询,并且仅当结果存在时,我才会发出第二个查询。然后我会对第三个查询应用相同的基本原理,最后,如果最后一个结果也存在,我会发送邀请。当前两个结果中的任何一个不存在时,这将避免对数据库的不必要调用。
In order to make the code more readable, testable and maintainable, I'd also extract each DB call to its own private method, chaining them with Optional.ifPresent
:
为了使代码更具可读性、可测试性和可维护性,我还将每个 DB 调用提取到其自己的私有方法,并将它们链接到Optional.ifPresent
:
public void sendInvite(Long targetId, Long sourceId, Long meetingId) {
userRepository.findById(targetId)
.ifPresent(target -> sendInvite(target, sourceId, meetingId));
}
private void sendInvite(User target, Long sourceId, Long meetingId) {
userRepository.findById(sourceId)
.map(User::getName)
.ifPresent(sourceName -> sendInvite(target, sourceName, meetingId));
}
private void sendInvite(User target, String sourceName, Long meetingId) {
eventRepository.findById(meetingId)
.map(Event::getName)
.ifPresent(meetingName -> sendInvite(target, sourceName, meetingName));
}
private void sendInvite(User target, String sourceName, String meetingName) {
String contents = String.format(
"Hi %s, $s has invited you to $s",
target.getName(),
sourceName,
meetingName);
sendInvite(target.getEmail(), contents);
}
回答by Oleksandr Pyrohov
The first approach is not perfect (it does not support laziness - all 3 database calls will be triggered anyway):
第一种方法并不完美(它不支持懒惰 - 无论如何都会触发所有 3 个数据库调用):
Optional<User> target = userRepository.findById(id1);
Optional<String> sourceName = userRepository.findById(id2).map(User::getName);
Optional<String> eventName = eventRepository.findById(id3).map(Event::getName);
if (Stream.of(target, sourceName, eventName).anyMatch(obj -> !obj.isPresent())) {
return;
}
sendInvite(target.get(), sourceName.get(), eventName.get());
The following example is a little bit verbose, but it supports laziness and readability:
下面的例子有点冗长,但它支持懒惰和可读性:
private void sendIfValid() {
Optional<User> target = userRepository.findById(id1);
if (!target.isPresent()) {
return;
}
Optional<String> sourceName = userRepository.findById(id2).map(User::getName);
if (!sourceName.isPresent()) {
return;
}
Optional<String> eventName = eventRepository.findById(id3).map(Event::getName);
if (!eventName.isPresent()) {
return;
}
sendInvite(target.get(), sourceName.get(), eventName.get());
}
private void sendInvite(User target, String sourceName, String eventName) {
// ...
}
回答by WorldSEnder
You can use the following if you want to stick to Optional
and not commit to consuming the value immediately. It makes use of Triple<L, M, R>
from Apache Commons:
如果您想坚持Optional
而不是立即使用该值,则可以使用以下方法。它利用了Triple<L, M, R>
Apache Commons:
/**
* Returns an optional contained a triple if all arguments are present,
* otherwise an absent optional
*/
public static <L, M, R> Optional<Triple<L, M, R>> product(Optional<L> left,
Optional<M> middle, Optional<R> right) {
return left.flatMap(l -> middle.flatMap(m -> right.map(r -> Triple.of(l, m, r))));
}
// Used as
product(maybeTarget, maybeSourceName, maybeEventName).ifPresent(this::sendInvite);
One could imagine a similar approach for two, or multiple Optional
s, although java unfortunately doesn't have a general tuple type (yet).
可以想象两个或多个Optional
s的类似方法,尽管不幸的是 java 还没有通用的元组类型(还)。
回答by Marco R.
You can create an infrastructure to handle a variable amount of inputs. For this to be a good design though, your inputs should not be Optional<?>
; but Supplier<Optional<?>>
so you can short-circuitthe unnecessary evaluation of Optionals
while trying to determine whether or not all are present.
您可以创建一个基础设施来处理可变数量的输入。尽管如此,为了这是一个好的设计,你的输入不应该是Optional<?>
; 但是Supplier<Optional<?>>
这样您就可以在尝试确定是否所有都存在时缩短不必要的评估Optionals
。
Because of this, it'd be better to create a utility wrapper around your Optional
s that provides transparent access to the evaluated value using a singleton pattern, like the following:
因此,最好在您的Optional
s周围创建一个实用程序包装器,它使用单例模式提供对评估值的透明访问,如下所示:
class OptionalSupplier {
private final Supplier<Optional<?>> optionalSupplier;
private Optional<?> evaluatedOptional = null;
public OptionalSupplier(Supplier<Optional<?>> supplier) {
this.optionalSupplier = supplier;
}
public Optional<?> getEvaluatedOptional() {
if (evaluatedOptional == null)
evaluatedOptional = optionalSupplier.get();
return evaluatedOptional;
}
}
Then you can create another class that handles a List
of these wrappers and provides a programmatic API to execute a Function
that takes as parameters the evaluated values of the actual optionals, hiding further the users involvement in the process. You can overload the method to execute a Consumer
with the same parameters. Such class would look something like this:
然后,您可以创建另一个类来处理List
这些包装器中的一个,并提供一个编程 API 来执行一个Function
将实际选项的评估值作为参数的类,从而进一步隐藏用户参与该过程。您可以重载方法以执行Consumer
具有相同参数的 a。这样的类看起来像这样:
class OptionalSemaphores {
private List<OptionalSupplier> optionalSuppliers;
private List<Object> results = null;
private boolean allPresent;
public OptionalSemaphores(Supplier<Optional<?>>... suppliers) {
optionalSuppliers = Stream.of(suppliers)
.map(OptionalSupplier::new)
.collect(Collectors.toList());
allPresent = optionalSuppliers.stream()
.map(OptionalSupplier::getEvaluatedOptional)
.allMatch(Optional::isPresent);
if (allPresent)
results = optionalSuppliers.stream()
.map(OptionalSupplier::getEvaluatedOptional)
.map(Optional::get)
.collect(Collectors.toList());
}
public boolean isAllPresent() {
return allPresent;
}
public <T> T execute(Function<List<Object>, T> function, T defaultValue) {
return (allPresent) ? function.apply(results) : defaultValue;
}
public void execute(Consumer<List<Object>> function) {
if (allPresent)
function.accept(results);
}
}
Finally all you have left to do is to create objects of this class (OptionalSemaphores
) using Supplier
s of your Optional
s (Supplier<Optional<?>>
) and invoking any of the overloaded execute
methods to run (IFall Optional
s are present) with a List
containing the corresponding evaluated values from your Optional
s. The following is a full working demo of this:
最后,您剩下要做的就是OptionalSemaphores
使用Supplier
您的Optional
s ( Supplier<Optional<?>>
) 的s创建此类 ( ) 的对象,并调用任何重载execute
方法来运行(如果所有Optional
s 都存在),其中List
包含来自您的Optional
s的相应评估值。以下是一个完整的工作演示:
public class OptionalsTester {
public static void main(String[] args) {
Supplier<Optional<?>> s1 = () -> Optional.of("Hello");
Supplier<Optional<?>> s2 = () -> Optional.of(1L);
Supplier<Optional<?>> s3 = () -> Optional.of(55.87);
Supplier<Optional<?>> s4 = () -> Optional.of(true);
Supplier<Optional<?>> s5 = () -> Optional.of("World");
Supplier<Optional<?>> failure = () -> Optional.ofNullable(null);
Supplier<Optional<?>> s7 = () -> Optional.of(55);
System.out.print("\nFAILING SEMAPHORES: ");
new OptionalSemaphores(s1, s2, s3, s4, s5, failure, s7).execute(System.out::println);
System.out.print("\nSUCCESSFUL SEMAPHORES: ");
new OptionalSemaphores(s1, s2, s3, s4, s5, s7).execute(System.out::println);
}
static class OptionalSemaphores {
private List<OptionalSupplier> optionalSuppliers;
private List<Object> results = null;
private boolean allPresent;
public OptionalSemaphores(Supplier<Optional<?>>... suppliers) {
optionalSuppliers = Stream.of(suppliers)
.map(OptionalSupplier::new)
.collect(Collectors.toList());
allPresent = optionalSuppliers.stream()
.map(OptionalSupplier::getEvaluatedOptional)
.allMatch(Optional::isPresent);
if (allPresent)
results = optionalSuppliers.stream()
.map(OptionalSupplier::getEvaluatedOptional)
.map(Optional::get)
.collect(Collectors.toList());
}
public boolean isAllPresent() {
return allPresent;
}
public <T> T execute(Function<List<Object>, T> function, T defaultValue) {
return (allPresent) ? function.apply(results) : defaultValue;
}
public void execute(Consumer<List<Object>> function) {
if (allPresent)
function.accept(results);
}
}
static class OptionalSupplier {
private final Supplier<Optional<?>> optionalSupplier;
private Optional<?> evaluatedOptional = null;
public OptionalSupplier(Supplier<Optional<?>> supplier) {
this.optionalSupplier = supplier;
}
public Optional<?> getEvaluatedOptional() {
if (evaluatedOptional == null)
evaluatedOptional = optionalSupplier.get();
return evaluatedOptional;
}
}
}
Hope this helps.
希望这可以帮助。
回答by Eugene
Well I took the same approach of Federicoto only call the DB when needed, it's quite verbose too, but lazy. I also simplified this a bit. Considering you have these 3 methods:
好吧,我采用了Federico的相同方法,只在需要时调用数据库,它也很冗长,但很懒惰。我也简化了一点。考虑到您有以下 3 种方法:
public static Optional<String> firstCall() {
System.out.println("first call");
return Optional.of("first");
}
public static Optional<String> secondCall() {
System.out.println("second call");
return Optional.empty();
}
public static Optional<String> thirdCall() {
System.out.println("third call");
return Optional.empty();
}
I've implemented it like this:
我是这样实现的:
firstCall()
.flatMap(x -> secondCall().map(y -> Stream.of(x, y))
.flatMap(z -> thirdCall().map(n -> Stream.concat(z, Stream.of(n)))))
.ifPresent(st -> System.out.println(st.collect(Collectors.joining("|"))));
回答by Roland Illig
If you treat Optional
just as a marker for method return values, the code becomes very simple:
如果你Optional
只是把它当作方法返回值的标记,代码就变得很简单了:
User target = userRepository.findById(id1).orElse(null);
User source = userRepository.findById(id2).orElse(null);
Event event = eventRepository.findById(id3).orElse(null);
if (target != null && source != null && event != null) {
String message = String.format("Hi %s, %s has invited you to %s",
target.getName(), source.getName(), event.getName());
sendInvite(target.getEmail(), message);
}
The point of Optional
is not that you must use it everywhere. Instead, it serves as a marker for method return values to inform the caller to check for absentness. In this case, the orElse(null)
takes care of this, and the calling code is fully concious about the possible nullness.
重点Optional
不是你必须在任何地方使用它。相反,它用作方法返回值的标记,以通知调用者检查缺席。在这种情况下,theorElse(null)
会处理这个问题,并且调用代码完全意识到可能的 null 值。