Lambdas,多个forEach与铸造

Dav*_*ins 27 java lambda java-8 java-stream

需要一些帮助思考来自我的同伴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;
}
Run Code Online (Sandbox Code Playgroud)

请注意,列表本身将作为客户端发送JSON,因此请不要关注返回的内容.必须是一些简洁的方法,我可以减少循环.

有兴趣看看我的同事专家创造的东西.鼓励多种方法.

编辑

findServices两个findChildren方法返回数组

编辑 - 奖金挑战

"不重要的部分"确实变得很重要.我实际上需要复制仅在host实例中可用的值.这似乎毁了所有美丽的例子.如何让国家前进?

final ContextInfo info = new ContextInfo(context.getPath());
info.setHostname(host.getName()); // The Bonus Challenge
Run Code Online (Sandbox Code Playgroud)

Stu*_*rks 36

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

第一个观察是,如果for循环转换为流,嵌套的for循环可以使用"平坦化"为单个流flatMap.此操作采用单个元素并在流中返回任意数字元素.我查了一下,发现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;
}
Run Code Online (Sandbox Code Playgroud)

但等等,还有更多.

最后的forEach操作是一个稍微复杂的map操作,将a转换Context为a 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());
}
Run Code Online (Sandbox Code Playgroud)

我通常会尝试避免多行lambda(例如在最后的map操作中),所以我将它重构为一个小的辅助方法,它接受一个Context并返回一个ContextInfo.这根本不会缩短代码,但我认为它确实使它更清晰.

UPDATE

但等等,还有更多.

让我们将调用解压缩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()))
        // ...
Run Code Online (Sandbox Code Playgroud)

这暴露了重复的过滤,instanceof然后是使用强制转换的映射.这总共完成了三次.似乎其他代码可能需要做类似的事情,所以将这些逻辑提取到辅助方法中会很好.问题是,filter可以更改流中的元素数量(丢弃不匹配的元素),但不能更改其类型.并且map可以改变元素的类型,但它不能改变它们的数量.有什么东西可以改变数量和类型吗?是的,这是我们的老朋友flatMap了!所以我们的helper方法需要获取一个元素并返回一个不同类型的元素流.该返回流将包含单个转换元素(如果匹配)或它将为空(如果它不匹配).辅助函数如下所示:

<T,U> Stream<U> toType(T t, Class<U> clazz) {
    if (clazz.isInstance(t)) {
        return Stream.of(clazz.cast(t));
    } else {
        return Stream.empty();
    }
}
Run Code Online (Sandbox Code Playgroud)

(这很基于OfType一些评论中提到的C#的结构.)

当我们在它的时候,让我们提取一个方法来创建一个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;
}
Run Code Online (Sandbox Code Playgroud)

在这些提取之后,管道看起来像这样:

    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());
Run Code Online (Sandbox Code Playgroud)

好吧,我想,我们已经删除了可怕的多行语句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());
Run Code Online (Sandbox Code Playgroud)


Edw*_*rzo 26

这将是我使用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());
Run Code Online (Sandbox Code Playgroud)

在这种方法中,我替换了过滤谓词的if语句.考虑到instanceof支票可以替换为Predicate<T>

Predicate<Object> isEngine = someObject -> someObject instanceof Engine;
Run Code Online (Sandbox Code Playgroud)

也可以表示为

Predicate<Object> isEngine = Engine.class::isInstance
Run Code Online (Sandbox Code Playgroud)

同样,您的演员表可以替换为Function<T,R>.

Function<Object,Engine> castToEngine = someObject -> (Engine) someObject;
Run Code Online (Sandbox Code Playgroud)

这几乎是一样的

Function<Object,Engine> castToEngine = Engine.class::cast;
Run Code Online (Sandbox Code Playgroud)

手动将项目添加到for循环中的列表可以用收集器替换.在生产代码中,将a变换ContextContextInfocan(并且应该)的lambda 被提取到单独的方法中,并用作方法引用.

  • 有趣地使用`Class :: isInstance`和`Class :: cast`的方法引用. (8认同)
  • 心神.吹....所有.过度.的.地板. (6认同)
  • @HighCore为了获得更多的参考透明度,我们可以使用map lambda来创建上下文信息,终端方法将是`collect(Collectors.toList())`,然后可以用它来进行原始列表的变异或简单地代替它.不幸的是,Streams API距离LINQ还有几年的时间.也许在另外4或5年:-) (2认同)
  • @HighCore:修复.映射和使用收集器(正如我刚才看到的Edwin Dalorzo建议的那样)是无副作用的方法. (2认同)