Lambdas,多个 forEach 与 cast

2022-09-01 12:39:24

需要一些帮助来思考来自我的StackOverflow名人的lambdas。

通过列表的列表进行挑选以在图形深处收集一些孩子的标准情况。哪些很棒的方法可以帮助这个样板?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

有兴趣看看我的专家同事创造了什么。鼓励采取多种方法。

编辑

和 两个方法返回数组findServicesfindChildren

编辑 - 奖金挑战

“不重要的部分”确实被证明是重要的。我实际上需要复制仅在实例中可用的值。这似乎毁掉了所有美丽的例子。如何将状态向前推进?host

final ContextInfo info = new ContextInfo(context.getPath());
info.setHostname(host.getName()); // The Bonus Challenge

答案 1

它的嵌套相当深,但似乎并不是特别困难。

第一个观察结果是,如果 for 循环转换为流,则可以使用 将嵌套的 for 循环“平展”为单个流。此操作采用单个元素,并在流中返回任意数量的元素。我查了一下,发现返回一个数组,所以我们用把它变成一个流。(我对 和 做了类似的假设。flatMapStandardServer.findServices()ServiceArrays.stream()Engine.findChildren()Host.findChildren()

接下来,每个循环中的逻辑执行检查和强制转换。这可以使用流作为执行操作的操作进行建模,然后执行仅强制转换并返回相同引用的操作。这实际上是一个no-op,但它允许静态类型系统将a转换为a。instanceoffilterinstanceofmapStream<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;
}

但是等等,还有更多。

最终操作是一个稍微复杂一些的操作,它将 a 转换为 .此外,这些只是收集到一个中,因此我们可以使用收集器来执行此操作,而不是在前面创建和空列表,然后填充它。应用这些重构会产生以下结果:forEachmapContextContextInfoList

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(例如在最终操作中),所以我会将其重构为一个小的帮助器方法,该方法采用 a 并返回 .这根本不会缩短代码,但我认为它确实使它更清晰。mapContextContextInfo

更新

但是等等,还有更多。

让我们将调用提取到它自己的管道元素中: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()))
        // ...

这暴露了重复过滤,然后是带有强制转换的映射。这总共做了三次。似乎其他代码可能需要执行类似操作,因此将这部分逻辑提取到帮助器方法中会很好。问题是可以更改流中的元素数量(删除不匹配的元素),但无法更改其类型。并且可以更改元素的类型,但不能更改其数量。某些东西可以同时改变数量和类型吗?是的,又是我们的老朋友了!因此,我们的帮助器方法需要获取一个元素并返回不同类型的元素流。该返回流将包含单个强制转换的元素(如果匹配)或为空(如果它不匹配)。帮助程序函数将如下所示:instanceoffiltermapflatMap

<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。

更新:奖金挑战

再一次,是你的朋友。取流尾,并将其迁移到尾部之前的最后一个。这样,变量仍在作用域内,您可以将其传递给已修改为也采用的帮助器方法。flatMapflatMaphostmakeContextInfohost

    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());

答案 2

这将是我使用 JDK 8 流、方法引用和 lambda 表达式的代码版本:

server.findServices()
    .stream()
    .map(Service::getContainer)
    .filter(Engine.class::isInstance)
    .map(Engine.class::cast)
    .flatMap(engine -> Arrays.stream(engine.findChildren()))
    .filter(Host.class::isInstance)
    .map(Host.class::cast)
    .flatMap(host -> Arrays.stream(host.findChildren()))
    .filter(Context.class::isInstance)
    .map(Context.class::cast)
    .map(context -> {
        ContextInfo info = new ContextInfo(context.getPath());
        info.setThisPart(context.getThisPart());
        info.setNotImportant(context.getNotImportant());
        return info;
    })
    .collect(Collectors.toList());

在此方法中,我将 if 语句替换为筛选器谓词。考虑到支票可以替换为instanceofPredicate<T>

Predicate<Object> isEngine = someObject -> someObject instanceof Engine;

也可以表示为

Predicate<Object> isEngine = Engine.class::isInstance

同样,您的强制转换也可以替换为 。Function<T,R>

Function<Object,Engine> castToEngine = someObject -> (Engine) someObject;

这与

Function<Object,Engine> castToEngine = Engine.class::cast;

在 for 循环中手动将项目添加到列表中可以替换为收集器。在生产代码中,将 a 转换为 can(并且应该)的 lambda 被提取到一个单独的方法中,并用作方法参考。ContextContextInfo


推荐