Lambdas,多个forEach与铸造 [英] Lambdas, multiple forEach with casting

查看:88
本文介绍了Lambdas,多个forEach与铸造的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

需要一些帮助思考来自我的同伴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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆