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

16

我相信你自己已经多次见过这种情况:

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是否在特权用户列表中。如果不在,则检查用户ID是否在允许用户列表中。否则,返回null。(这不是最好的例子,但我希望它足够生动)。
Guava为我们提供了一些帮助程序来美化上述代码。
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(见下文)的功能?是否有一种使用新的Stream框架轻松实现此目的的简单 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 -> queryXXXXSource(args)可以缩短为queryXXXXSource。 但是,这样做会使建议的解决方案更难阅读,因为一眼看上去不清楚会发生什么。

3个回答

19

是的,有:

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

这种方式更加灵活,如果找到非空的source,它会返回一个Optional而不是null

编辑:如果您需要惰性求值,可以使用Supplier

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

4
如果你想从可变参数中创建一个Stream,那么你可以直接调用Stream.of(source1, source2, ...)而不是Arrays.asList(source1, source2, ...).stream() - Helder Pereira

14

这取决于你没有定义的一些因素。你是否有一个固定的、相当小的 query…Source 操作集,就像你在问题中展示的那样,还是你更倾向于拥有一个更灵活、可扩展的操作列表?

在第一种情况下,你可以考虑将 query…Source 方法更改为返回一个 Optional<SomeObject> 而不是 SomeObjectnull。如果你像下面这样更改你的方法

Optional<SomeObject> queryFirstSource(Arguments args) {}
你可以这样链接它们:

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

如果你无法改变它们或者更喜欢它们返回null,你仍然可以使用Optional类:

如果您无法更改它们或偏好它们返回null,则仍可使用Optional类:
public SomeObject findSomeObject(Arguments args) {
    return Optional.ofNullable(queryFirstSource(args)).orElseGet(
       ()->Optional.ofNullable(querySecondSource(args)).orElseGet(
       ()->queryThirdSource(args)));
}
如果您正在寻找一种更灵活的方式来处理更多可能的查询,那么不可避免地需要将它们转换为某种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 语句序列也是合适的。


我选择这个答案作为我的指定答案,因为它结合了所有其他答案,因此似乎是“完整的”。我不知道Optional<>类,我认为它为我的问题提供了最优雅的解决方式。 - cimnine

7
我会这样写(你可能不需要泛型,但为什么不使用呢):
public static <A, T> Optional<T> findFirst(Predicate<T> predicate, A argument,
                                         Function<A, T>... functions) {
  return Arrays.stream(functions)
          .map(f -> f.apply(argument))
          .filter(predicate::test)
          .findFirst();
}

你可以使用以下方式调用:

return findFirst(Objects::nonNull, args, this::queryFirstSource,
                                       this::querySecondSource,
                                       this::queryThirdSource);

(假设你的queryXXX方法是实例方法)
这些方法将按顺序应用,直到返回与谓词匹配的值(在上面的示例中:返回一个非空值)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接