Lambdas,多个forEach与铸造 [英] Lambdas, multiple forEach with casting
问题描述
需要一些帮助思考来自我的同伴StackOverflow名人的lambda。
Need some help thinking in lambdas from my fellow StackOverflow luminaries.
通过列表列表挑选一些列表中的一些孩子的标准情况图形。什么样的方法可以 Lambdas
帮助这个样板?
Standard case of picking through a list of a list of a list to collect some children deep in a graph. What awesome ways could Lambdas
help with this boilerplate?
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
,所以不要关注返回的内容。必须是一些简洁的方法,我可以减少循环。
Note the list itself is going to the client as JSON
, so don't focus on what is returned. Must be a few neat ways I can cut down the loops.
有兴趣看看我的同事专家创建。鼓励采用多种方法。
Interested to see what my fellow experts create. Multiple approaches encouraged.
编辑
findServices
和两个 findChildren
方法返回数组
编辑 - 奖金挑战
不重要的部分确实很重要。我实际上需要复制仅在主机
实例中可用的值。这似乎毁了所有美丽的例子。如何携带州前进?
The "not important part" did turn out to be important. I actually need to copy a value only available in the host
instance. This seems to ruin all the beautiful examples. How would one carry state forward?
final ContextInfo info = new ContextInfo(context.getPath());
info.setHostname(host.getName()); // The Bonus Challenge
推荐答案
这是相当深的嵌套但是它似乎并不是特别困难。
It's fairly deeply nested but it doesn't seem exceptionally difficult.
第一个观察结果是,如果for循环转换为流,嵌套的for循环可以展平为单个流使用 flatMap
。此操作采用单个元素并在流中返回任意数字元素。我查了一下,发现 StandardServer.findServices()
返回一个 Service
的数组,所以我们把它变成一个流 Arrays.stream()
。 (我对 Engine.findChildren()
和 Host.findChildren()
做出了类似的假设。
The first observation is that if a for-loop translates into a stream, nested for-loops can be "flattened" into a single stream using flatMap
. This operation takes a single element and returns an arbitrary number elements in a stream. I looked up and found that StandardServer.findServices()
returns an array of Service
so we turn this into a stream using Arrays.stream()
. (I make similar assumptions for Engine.findChildren()
and Host.findChildren()
.
接下来,每个循环中的逻辑执行 instanceof
检查和转换。这可以使用stream作为过滤
操作以执行 instanceof
,然后执行 map
操作,只需转换并返回这实际上是一个无操作,但它允许静态类型系统将 Stream< Container>
转换为 Stream< Host>
例如。
Next, the logic within each loop does an instanceof
check and a cast. This can be modeled using streams as a filter
operation to do the instanceof
followed by a map
operation that simply casts and returns the same reference. This is actually a no-op but it lets the static typing system convert a Stream<Container>
to a Stream<Host>
for example.
将这些转换应用于嵌套循环,我们得到以下结果:
Applying these transformations to the nested loops, we get the following:
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
操作,可以转换上下文
到 ContextInfo
。此外,这些只是收集到列表
中,因此我们可以使用收集器来执行此操作,而不是先创建并清空列表然后填充它。应用这些重构会产生以下结果:
The final forEach
operation is a slightly more complicated map
operation that converts a Context
into a ContextInfo
. Furthermore, these are just collected into a List
so we can use collectors to do this instead of creating and empty list up front and then populating it. Applying these refactorings results in the following:
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());
}
我通常会尝试避免多行lambdas(例如在最后的 map
operation)所以我将它重构为一个小帮助方法,它接受一个 Context
并返回一个 ContextInfo
。这根本不会缩短代码,但我认为它确实更清晰。
I usually try to avoid multi-line lambdas (such as in the final map
operation) so I'd refactor it into a little helper method that takes a Context
and returns a ContextInfo
. This doesn't shorten the code at all, but I think it does make it clearer.
更新
但等等,还有更多。
让我们解析对的调用service.getContainer()
进入自己的管道元素:
Let's extract the call to service.getContainer()
into its own pipeline element:
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
上重复过滤,然后使用强制转换进行映射。这总共完成了三次。似乎其他代码可能需要做类似的事情,所以将这一部分逻辑提取到辅助方法中会很好。问题是过滤器
可以更改流中的元素数(删除不匹配的元素),但不能更改其类型。并且 map
可以更改元素的类型,但不能更改它们的数量。有什么东西可以改变数量和类型吗?是的,这是我们的老朋友 flatMap
了!所以我们的辅助方法需要获取一个元素并返回不同类型的元素流。该返回流将包含单个转换元素(如果匹配)或它将为空(如果它不匹配)。辅助函数如下所示:
This exposes the repetition of filtering on instanceof
followed by a mapping with a cast. This is done three times in total. It seems likely that other code is going to need to do similar things, so it would be nice to extract this bit of logic into a helper method. The problem is that filter
can change the number of elements in the stream (dropping ones that don't match) but it can't change their types. And map
can change the types of elements, but it can't change their number. Can something change both the number and types? Yes, it's our old friend flatMap
again! So our helper method needs to take an element and return a stream of elements of a different type. That return stream will contain a single casted element (if it matches) or it will be empty (if it doesn't match). The helper function would look like this:
<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
在一些评论中提到的构造。)
(This is loosely based on C#'s OfType
construct mentioned in some of the comments.)
当我们在它时,让我们提取一个方法来创建一个 ContextInfo
:
While we're at it, let's extract a method to create a 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;
}
在这些提取之后,管道如下所示:
After these extractions, the pipeline looks like this:
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。
Nicer, I think, and we've removed the dreaded multi-line statement lambda.
更新:奖金挑战
再一次, flatMap
是你的朋友。获取流的尾部并将其迁移到尾部之前的最后一个 flatMap
。这样,主机
变量仍在范围内,您可以将其传递给已被修改为的 makeContextInfo
辅助方法也可以主机
。
Once again, flatMap
is your friend. Take the tail of the stream and migrate it into the last flatMap
before the tail. That way the host
variable is still in scope, and you can pass it to a makeContextInfo
helper method that's been modified to take host
as well.
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());
这篇关于Lambdas,多个forEach与铸造的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!