在我的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变量仍在范围内,您可以将其传递到已修改为也接受makeContextInfohost帮助器方法。
    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());

10-07 13:36
查看更多