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

2022-09-05 00:29:03

你自己已经看过很多次了,我敢肯定:

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。(这不是最好的例子,但我希望这是一个足够生动的例子。

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

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

与第一个示例相反,在第一个示例中,仅当前者不返回某些内容时才评估下一个源,而第二个解决方案最初可能看起来更好,但性能可能差得多。

这就是我们提出我的实际问题的地方,也是我建议的一些东西,我希望有人已经实现了它的基础,或者有人可能会提出一个甚至聪明的解决方案。

用简单的英语:有人已经实现了这样一个defferedFirstNonNull(见下文)或类似的东西吗?有没有一个简单的普通Java解决方案可以通过新的Stream框架来实现这一点?您能提出另一个实现相同结果的优雅解决方案吗?

规则: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
     )
 }

因此,该方法将一个接一个地计算每个lambda表达式,并且一旦谓词()为真(即我们找到了匹配项),该方法将立即返回结果,并且不会查询任何进一步的源。defferedFirstNonNullx -> x != null

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


答案 1

是的,有:

Arrays.asList(source1, source2, ...)
   .stream()
   .filter(s -> s != null)
   .findFirst();

这更灵活,因为它在找到非空的情况下返回 not。Optionalnullsource

编辑:如果你想要懒惰的计算,你应该使用:Supplier

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

答案 2

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

在第一种情况下,您可以考虑将方法更改为返回 a 而不是 or 。如果你改变你的方法,就像query…SourceOptional<SomeObject>SomeObjectnull

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

您可以通过以下方式链接它们:

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

如果您无法更改它们或希望它们返回,您仍然可以使用该类:nullOptional

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

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

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);
}

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


推荐