有一种优雅的方法来获取Java中多个方法返回的第一个非null值吗? [英] Is there an elegant way to get the first non null value of multiple method returns in Java?

查看:142
本文介绍了有一种优雅的方法来获取Java中多个方法返回的第一个非null值吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

你已经多次见过这种情况了,我敢肯定:

You have already seen this many times yourself, of that I'm sure:

public SomeObject findSomeObject(Arguments args) {
    SomeObject so = queryFirstSource(args); // the most likely source first, hopefully
    if (so != null) return so;

    so = querySecondSource(args); // a source less likely than the first, hopefully
    if (so != null) return so;

    so = queryThirdSource(args); // a source less likely than the previous, hopefully
    if (so != null) return so;

    // and so on
}

我们有不同我们搜索的对象可能是的来源。作为一个更生动的例子,我们可以想象我们首先检查用户ID是否在特权用户列表中。如果不是,我们检查用户标识是否在允许的用户列表中。否则我们返回null。 (这不是最好的例子,但我希望它足够生动。)

We have different sources where an object we search could be. As a more vivid example we could image that we first check if a userid is in a list of privileged users. If not we check if the userid is in the list of allowed users. Else we return null. (It's not the best example but I hope it's a vivid-enough one.)

Guava 为我们提供了一些可以美化上述代码的助手:

Guava offers us some helpers that could beautify that code above:

public SomeObject findSomeObject(Arguments args) {
    // if there are only two objects
    return com.google.common.base.Objects.firstNonNull(queryFirstSource(args), querySecondSource(args));

    // or else
    return com.google.common.collect.Iterables.find(
        Arrays.asList(
            queryFirstSource(args)
            , querySecondSource(args)
            , queryThirdSource(args)
            // , ...
        )
        , com.google.common.base.Predicates.notNull()
    );
 }

但是,正如我们中间经验丰富的人所见,这可能会有所表现如果查找(即 queryXXXXSource(args))需要一定的时间,那就太糟糕了。这是因为我们现在首先查询所有源,然后将结果传递给找到那些不是 null 的结果中的第一个的方法。

But, as the more experienced among us will have already seen, this may perform bad if the lookups (i.e. queryXXXXSource(args)) take a certain amount of time. This is because we now query all sources first and then pass the results to the method that finds the first among those results which is not null.

与第一个示例相比,下一个源仅在前者没有返回的情况下进行评估时,第二个解决方案最初可能看起来更好,但可能表现更差。

In contrast to the first example, where the next source is only evaluated when the former does not return something, this second solution may look better at first but could perform much worse.

这里是我们提出实际问题的地方,也是我建议的地方,我希望有人已经实现了它的基础,或者有人可能会提出一个更智能的解决方案。

Here's where we come to my actual question and to where I suggest something of that I hope someone has already implemented the base of it or of that someone might propose a even smarted solution.

用简单的英语:有人已经实现了这样的 defferedFirstNonNull (见下文)或类似的东西?使用新的框架?您能否提出另一个优雅的解决方案,以达到相同的效果?

In plain English: Has someone already implemented such a defferedFirstNonNull (see below) or something similar? Is there an easy plain-Java solution to achieve this with the new Stream framework? Can you propose another elegant solution that achieves the same result?

规则:允许Java 8以及主动维护和众所周知的第三方图书馆,如谷歌的Guava或Apache的Commons Lang,带有Apache许可证或类似版本(没有GPL!)。

Rules: Java 8 is allowed as well as active maintained and well-known third party libraries like Google's Guava or Apache's Commons Lang with Apache License or similar (No GPL!).

建议的解决方案:

public SomeObject findSomeObject(Arguments args) {
    return Helper.deferredFirstNonNull(
        Arrays.asList(
            args -> queryFirstSource(args)
            , args -> querySourceSource(args)
            , args -> queryThirdSource(args)
        )
        , x -> x != null
     )
 }

所以方法 defferedFirstNonNull 将逐个评估每个lambda表达式,并且只要谓词( x - > x!= null )为真(即我们找到匹配项)方法会立即返回结果,但不会查询任何进一步的来源。

So the method defferedFirstNonNull would evaluate each lambda expression after another and as soon as the predicate (x -> x != null) is true (i.e. we found a match) the method would return the result immediately and would not query any further source.

PS:我知道表达式 args - > queryXXXXSource(args)可缩短为 queryXXXXSource 。但这会使提议的解决方案更难以阅读,因为第一眼看到的情况并不明显会发生什么。

PS: I know that the expressions args -> queryXXXXSource(args) could be shortened to queryXXXXSource. But that would render the proposed solution harder to read because it's not obvious on first sight what is going to happen.

推荐答案

这取决于关于你没有定义的一些因素。您是否有一个固定的,相当小的查询...来源您的问题中显示的操作,还是您希望获得更灵活,可扩展的操作列表?

It depends on some factors you are not defining. Do you have a fixed, rather small set of query…Source actions as shown in your question or are you rather heading to having a more flexible, extensible list of actions?

在第一种情况下,您可以考虑更改查询...来源方法以返回可选< SomeObject> 而不是 SomeObject null 。如果您将方法更改为

In the first case you might consider changing the query…Source methods to return an Optional<SomeObject> rather than SomeObject or null. If you change your methods to be like

Optional<SomeObject> queryFirstSource(Arguments args) {
    …
}

你可以链接它们方式:

public SomeObject findSomeObject(Arguments args) {
    return queryFirstSource(args).orElseGet(
      ()->querySecondSource(args).orElseGet(
      ()->queryThirdSource(args).orElse(null)));
}

如果您无法更改它们或希望它们返回 null 你仍然可以使用可选类:

If you can’t change them or prefer them to return null you can still use the Optional class:

public SomeObject findSomeObject(Arguments args) {
    return Optional.ofNullable(queryFirstSource(args)).orElseGet(
       ()->Optional.ofNullable(querySecondSource(args)).orElseGet(
       ()->queryThirdSource(args)));
}






如果您正在寻找对于大量可能的查询,更灵活的方式是,将它们转换为 Function s的某种列表或流是不可避免的。一种可能的解决方案是:


If you are looking for a more flexible way for a bigger number of possible queries, it is unavoidable to convert them to some kind of list or stream of Functions. One possible solution is:

public SomeObject findSomeObject(Arguments args) {
    return Stream.<Function<Arguments,SomeObject>>of(
      this::queryFirstSource, this::querySecondSource, this::queryThirdSource
    ).map(f->f.apply(args)).filter(Objects::nonNull).findFirst().orElse(null);
}

这会执行所需的操作,但每次都会构成必要的操作你调用方法。如果您想更频繁地调用此方法,可以考虑编写一个可以重复使用的操作:

This performs the desired operation, however, it will compose the necessary action every time you invoke the method. If you want to invoke this method more often, you may consider composing an operation which you can re-use:

Function<Arguments, SomeObject> find = Stream.<Function<Arguments,SomeObject>>of(
    this::queryFirstSource, this::querySecondSource, this::queryThirdSource
).reduce(a->null,(f,g)->a->Optional.ofNullable(f.apply(a)).orElseGet(()->g.apply(a)));

public SomeObject findSomeObject(Arguments args) {
    return find.apply(args);
}

所以你看,有不止一种方法。这取决于实际任务的方向。有时,即使是简单的 if 序列也可能是合适的。

So you see, there are more than one way. And it depends on the actual task what direction to go. Sometimes, even the simple if sequence might be appropriate.

这篇关于有一种优雅的方法来获取Java中多个方法返回的第一个非null值吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆