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

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

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

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

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

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

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

 Optional queryFirstSource(Arguments args) { … } 

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

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

如果您无法更改它们或者希望它们返回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.>of( this::queryFirstSource, this::querySecondSource, this::queryThirdSource ).map(f->f.apply(args)).filter(Objects::nonNull).findFirst().orElse(null); } 

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

 Function find = Stream.>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序列也可能是合适的。

就在这里:

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

这更灵活,因为如果找到非空source ,它返回一个Optional not null

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

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

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

 public static  Optional findFirst(Predicate predicate, A argument, Function... 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方法是实例方法)

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