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