在我的StackOverflow杰出专家的lambda中思考时,需要一些帮助。
从列表中的列表中选取列表以在图中深处收集一些子代的标准情况。 Lambdas
可通过此样板提供哪些很棒的方法?
public List<ContextInfo> list() {
final List<ContextInfo> list = new ArrayList<ContextInfo>();
final StandardServer server = getServer();
for (final Service service : server.findServices()) {
if (service.getContainer() instanceof Engine) {
final Engine engine = (Engine) service.getContainer();
for (final Container possibleHost : engine.findChildren()) {
if (possibleHost instanceof Host) {
final Host host = (Host) possibleHost;
for (final Container possibleContext : host.findChildren()) {
if (possibleContext instanceof Context) {
final Context context = (Context) possibleContext;
// copy to another object -- not the important part
final ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
list.add(info);
}
}
}
}
}
}
return list;
}
请注意,列表本身将以
JSON
的形式发送给客户端,因此不要专注于返回的内容。我必须采用几种简洁的方法来减少循环。有兴趣了解我的专家们创造的东西。鼓励采用多种方法。
编辑
findServices
和两个findChildren
方法返回数组编辑-奖励挑战
事实证明“不重要的部分”很重要。我实际上需要复制仅在
host
实例中可用的值。这似乎毁了所有美丽的例子。一个人如何将状态向前发展?final ContextInfo info = new ContextInfo(context.getPath());
info.setHostname(host.getName()); // The Bonus Challenge
最佳答案
它嵌套得很深,但似乎并不难。
第一个观察结果是,如果for循环转换为流,则可以使用flatMap
将嵌套的for循环“展平”为单个流。此操作采用单个元素,并在流中返回任意数量的元素。我抬起头,发现StandardServer.findServices()
返回了Service
数组,因此我们使用Arrays.stream()
将其转换为流。 (我对Engine.findChildren()
和Host.findChildren()
做出了类似的假设。
接下来,每个循环中的逻辑执行instanceof
检查和强制转换。可以使用流将其建模为filter
操作来执行instanceof
,然后使用map
操作来进行建模,该操作只需转换并返回相同的引用即可。这实际上是一个空操作,但是它可以让静态输入系统将Stream<Container>
转换为Stream<Host>
。
将这些转换应用于嵌套循环,我们得到以下信息:
public List<ContextInfo> list() {
final List<ContextInfo> list = new ArrayList<ContextInfo>();
final StandardServer server = getServer();
Arrays.stream(server.findServices())
.filter(service -> service.getContainer() instanceof Engine)
.map(service -> (Engine)service.getContainer())
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.filter(possibleHost -> possibleHost instanceof Host)
.map(possibleHost -> (Host)possibleHost)
.flatMap(host -> Arrays.stream(host.findChildren()))
.filter(possibleContext -> possibleContext instanceof Context)
.map(possibleContext -> (Context)possibleContext)
.forEach(context -> {
// copy to another object -- not the important part
final ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
list.add(info);
});
return list;
}
但是,等等,还有更多。
最后的
forEach
操作是稍微复杂一点的map
操作,它将Context
转换为ContextInfo
。此外,这些只是被收集到List
中,因此我们可以使用收集器来执行此操作,而不是先创建并清空列表然后填充它。应用这些重构将产生以下结果:public List<ContextInfo> list() {
final StandardServer server = getServer();
return Arrays.stream(server.findServices())
.filter(service -> service.getContainer() instanceof Engine)
.map(service -> (Engine)service.getContainer())
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.filter(possibleHost -> possibleHost instanceof Host)
.map(possibleHost -> (Host)possibleHost)
.flatMap(host -> Arrays.stream(host.findChildren()))
.filter(possibleContext -> possibleContext instanceof Context)
.map(possibleContext -> (Context)possibleContext)
.map(context -> {
// copy to another object -- not the important part
final ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
return info;
})
.collect(Collectors.toList());
}
我通常会尽量避免使用多行lambda(例如在最终的
map
操作中),因此我会将其重构为一个小的辅助方法,该方法需要Context
并返回ContextInfo
。这根本不会缩短代码,但我认为确实可以使代码更清晰。更新
但是,等等,还有更多。
让我们将对
service.getContainer()
的调用提取到其自己的管道元素中: return Arrays.stream(server.findServices())
.map(service -> service.getContainer())
.filter(container -> container instanceof Engine)
.map(container -> (Engine)container)
.flatMap(engine -> Arrays.stream(engine.findChildren()))
// ...
这暴露了对
instanceof
进行过滤的重复,然后进行了带有强制类型转换的映射。这总共完成了三遍。似乎其他代码也可能需要做类似的事情,因此最好将这部分逻辑提取到一个辅助方法中。问题在于filter
可以更改流中的元素数量(删除不匹配的元素),但是不能更改其类型。 map
可以更改元素的类型,但不能更改其数量。可以改变数字和类型吗?是的,又是我们的老 friend flatMap
!因此,我们的辅助方法需要获取一个元素并返回不同类型的元素流。该返回流将包含单个强制转换的元素(如果匹配),或者将为空(如果不匹配)。辅助函数如下所示:<T,U> Stream<U> toType(T t, Class<U> clazz) {
if (clazz.isInstance(t)) {
return Stream.of(clazz.cast(t));
} else {
return Stream.empty();
}
}
(这大致基于某些评论中提到的C#的
OfType
构造。)在此过程中,让我们提取一个创建
ContextInfo
的方法:ContextInfo makeContextInfo(Context context) {
// copy to another object -- not the important part
final ContextInfo info = new ContextInfo(context.getPath());
info.setThisPart(context.getThisPart());
info.setNotImportant(context.getNotImportant());
return info;
}
这些提取之后,管道如下所示:
return Arrays.stream(server.findServices())
.map(service -> service.getContainer())
.flatMap(container -> toType(container, Engine.class))
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.flatMap(possibleHost -> toType(possibleHost, Host.class))
.flatMap(host -> Arrays.stream(host.findChildren()))
.flatMap(possibleContext -> toType(possibleContext, Context.class))
.map(this::makeContextInfo)
.collect(Collectors.toList());
我认为,尼斯,而且我们已经删除了可怕的多行语句lambda。
更新:奖励挑战
再次,
flatMap
是您的 friend 。以流的尾部并将其迁移到尾部之前的最后flatMap
中。这样,host
变量仍在范围内,您可以将其传递到已修改为也接受makeContextInfo
的host
帮助器方法。 return Arrays.stream(server.findServices())
.map(service -> service.getContainer())
.flatMap(container -> toType(container, Engine.class))
.flatMap(engine -> Arrays.stream(engine.findChildren()))
.flatMap(possibleHost -> toType(possibleHost, Host.class))
.flatMap(host -> Arrays.stream(host.findChildren())
.flatMap(possibleContext -> toType(possibleContext, Context.class))
.map(ctx -> makeContextInfo(ctx, host)))
.collect(Collectors.toList());