它的嵌套相当深,但似乎并不特别困难。
第一个观察结果是,如果 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 上的重复过滤,然后是带有强制转换的映射。总共进行了 3 次。似乎其他代码可能需要做类似的事情,所以最好将这部分逻辑提取到辅助方法中。问题是filter 可以更改流中元素的数量(删除不匹配的元素),但不能更改它们的类型。而map 可以改变元素的类型,但不能改变它们的数量。可以改变数量和类型吗?是的,又是我们的老朋友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();
}
}
(这大致基于某些 cmets 中提到的 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 是你的朋友。取流的尾部并将其迁移到尾部之前的最后一个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());