在Java中,有没有一种优雅的方法可以获取多个方法返回的第一个非空值?

在Java中,有没有一种优雅的方法可以获取多个方法返回的第一个非空值?,java,lambda,guava,java-8,Java,Lambda,Guava,Java 8,你自己已经见过很多次了,我相信: 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

你自己已经见过很多次了,我相信:

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是否在特权用户列表中。如果没有,我们检查userid是否在允许的用户列表中。否则返回null。(这不是最好的例子,但我希望它足够生动。)

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

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()
    );
 }
但是,正如我们中更有经验的人已经看到的那样,如果查找(即
queryxxxsource(args)
)花费一定的时间,这可能会执行不好。这是因为我们现在首先查询所有源,然后将结果传递给在这些结果中找到第一个非
null
的方法

与第一个示例相反,在第一个示例中,下一个源仅在前一个源不返回某些内容时才进行评估,第二个解决方案一开始可能看起来更好,但性能可能更差

这就是我的实际问题所在,我提出了一些建议,我希望有人已经实现了它的基础,或者有人可能会提出一个更聪明的解决方案

简单地说:是否有人已经实现了这样一个
defferedFirstNonNull
(见下文)或类似的功能?有没有一个简单的Java解决方案可以在新框架中实现这一点?你能提出另一个达到同样效果的优雅解决方案吗

规则:允许使用Java 8以及活动维护的和知名的第三方库,如Google的Guava或Apache的Commons Lang,具有Apache许可证或类似许可证(无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
)为真(即,我们找到了匹配项),该方法将立即返回结果,并且不会查询任何进一步的源

PS:我知道表达式
args->queryxxxsource(args)
可以缩短为
queryxxxsource
。但是,这会使建议的解决方案更难阅读,因为第一眼就看不出会发生什么。

是的,有:

Arrays.asList(source1, source2, ...)
   .stream()
   .filter(s -> s != null)
   .findFirst();
这更灵活,因为如果找到非空
,它将返回一个
可选

编辑:如果您想要延迟评估,您应该使用
供应商

Arrays.<Supplier<Source>>asList(sourceFactory::getSource1, sourceFactory::getSource2, ...)
   .stream()
   .filter(s -> s.get() != null)
   .findFirst();
Arrays.asList(sourceFactory::getSource1,sourceFactory::getSource2,…)
.stream()
.filter(s->s.get()!=null)
.findFirst();

我会这样写(这里您可能不需要泛型,但为什么不这样做):

(假设您的
queryXXX
方法是实例方法)


方法将按顺序应用,直到返回与谓词匹配的值为止(在上面的示例中:返回非空值)。

这取决于您未定义的某些因素。您是否有一组固定的、相当小的
query…Source
操作,如您的问题所示,或者您是否打算拥有一个更灵活、可扩展的操作列表

在第一种情况下,您可以考虑更改<代码>查询…源代码>代码>方法来返回<代码>可选<代码>,而不是<代码>某个对象< /> >或<代码> null < /C>。如果你改变你的方法

Optional<SomeObject> queryFirstSource(Arguments args) {
    …
}
如果您无法更改它们或希望它们返回
null
,您仍然可以使用
可选的
类:

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

如果您正在寻找一种更灵活的方法来处理更多可能的查询,那么不可避免地要将它们转换为某种类型的列表或
函数流。一种可能的解决办法是:

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);
}
public SomeObject findSomeObject(参数args){
回流(
this::queryFirstSource、this::querySecondSource、this::queryThirdSource
).map(f->f.apply(args)).filter(Objects::nonNull).findFirst().orElse(null);
}
这将执行所需的操作,但是,它将在每次调用该方法时组成必要的操作。如果你想更频繁地调用这个方法,你可以考虑编写一个可以重用的操作:

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);
}
函数find=Stream.of(
this::queryFirstSource、this::querySecondSource、this::queryThirdSource
).reduce(a->null,(f,g)->a->Optional.ofNullable(f.apply(a)).orElseGet(()->g.apply(a));
公共SomeObject findSomeObject(参数args){
返回find.apply(args);
}

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

我选择这个答案作为我的指定答案,因为它结合了所有其他答案,因此看起来“完整”。我不知道
Optional
类,我认为它为我的问题提供了最优雅的解决方法。如果您想从varargs创建
Stream
,您可以调用
Stream.of(source1,source2,…)
而不是
Arrays.asList(source1,source2,…).Stream()
public SomeObject findSomeObject(Arguments args) {
    return Optional.ofNullable(queryFirstSource(args)).orElseGet(
       ()->Optional.ofNullable(querySecondSource(args)).orElseGet(
       ()->queryThirdSource(args)));
}
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);
}
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);
}