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

cim*_*ine 9 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 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
}
Run Code Online (Sandbox Code Playgroud)

我们有不同的来源,我们搜索的对象可能是.作为一个更生动的例子,我们可以想象我们首先检查用户标识是否在特权用户列表中.如果不是,我们检查用户标识是否在允许的用户列表中.否则我们返回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()
    );
 }
Run Code Online (Sandbox Code Playgroud)

但是,正如我们中间经验丰富的人已经看到的那样,如果查找(即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
     )
 }
Run Code Online (Sandbox Code Playgroud)

因此,该方法defferedFirstNonNull将逐个评估每个lambda表达式,并且一旦谓词(x -> x != null)为真(即我们找到匹配),该方法将立即返回结果,并且不会查询任何其他源.

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

m3t*_*man 12

就在这里:

Arrays.asList(source1, source2, ...)
   .stream()
   .filter(s -> s != null)
   .findFirst();
Run Code Online (Sandbox Code Playgroud)

这更灵活,因为它返回的Optional不是null在找不到null的情况下source.

编辑:如果你想要懒惰评估,你应该使用Supplier:

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

  • 如果你想从可变参数创建一个 `Stream`,你也可以调用 `Stream.of(source1, source2, ...)` 而不是 `Arrays.asList(source1, source2, ...).stream( )` (3认同)

Hol*_*ger 9

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

在第一种情况下,您可以考虑更改query…Source 方法以返回Optional<SomeObject>而不是SomeObjectnull.如果你改变你的方法就好

Optional<SomeObject> queryFirstSource(Arguments args) {
    …
}
Run Code Online (Sandbox Code Playgroud)

你可以用这种方式链接它们:

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

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

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

如果您正在寻找更灵活的方法来处理更多可能的查询,那么将它们转换为某种列表或流的流是不可避免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);
}
Run Code Online (Sandbox Code Playgroud)

这将执行所需的操作,但是,每次调用该方法时,它将组成必要的操作.如果要更频繁地调用此方法,可以考虑编写可以重复使用的操作:

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);
}
Run Code Online (Sandbox Code Playgroud)

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


ass*_*ias 5

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

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();
}
Run Code Online (Sandbox Code Playgroud)

你可以用它来调用它:

return findFirst(Objects::nonNull, args, this::queryFirstSource,
                                       this::querySecondSource,
                                       this::queryThirdSource);
Run Code Online (Sandbox Code Playgroud)

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

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