你自己已经多次见过这一点,我确信:
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。 (这不是最好的例子,但我希望它足够生动。)
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
(见下文)或类似的东西吗?有没有一个简单的普通Java解决方案来实现这个新的Stream框架?您能否提出另一种优雅的解决方案,以达到相同的效果?
规则:允许使用Java 8以及主动维护和知名的第三方库,例如Google的Guava或Apache的Apache Com License Lang等等(No 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
。但这会使提议的解决方案更难以阅读,因为它一眼就看不到会发生什么。
答案 0 :(得分:12)
是的,有:
Arrays.asList(source1, source2, ...)
.stream()
.filter(s -> s != null)
.findFirst();
这更灵活,因为如果找到非空Optional
,它会返回null
而非source
。
编辑:如果您想进行延迟评估,则应使用Supplier
:
Arrays.<Supplier<Source>>asList(sourceFactory::getSource1, sourceFactory::getSource2, ...)
.stream()
.filter(s -> s.get() != null)
.findFirst();
答案 1 :(得分:9)
这取决于您未定义的一些因素。您是否有一组固定的,相当小的query…Source
动作,如您的问题所示,或者您是否希望获得更灵活,可扩展的行动列表?
在第一种情况下,您可以考虑更改query…Source
方法以返回Optional<SomeObject>
而不是SomeObject
或null
。如果您将方法更改为
Optional<SomeObject> 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.<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
序列也可能是合适的。
答案 2 :(得分: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();
}
你可以用:
来调用它return findFirst(Objects::nonNull, args, this::queryFirstSource,
this::querySecondSource,
this::queryThirdSource);
(假设您的queryXXX
方法是实例方法)
将按顺序应用这些方法,直到返回一个与谓词匹配的值(在上面的示例中:返回非空值)。