Lambdas,多个forEach与铸造

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

选择列表列表以在图表中深入收集一些孩子的标准情况。 Lambdas有什么好方法可以帮助这个样板?

 public List list() { final List list = new ArrayList(); 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转换为Stream

将这些转换应用于嵌套循环,我们得到以下结果:

 public List list() { final List list = new ArrayList(); 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 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操作中),所以我将它重构为一个小的helper方法,它接受一个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())) // ... 

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

  Stream toType(T t, Class 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再次成为你的朋友。 取出流的尾部并将其移动到尾部之前的最后一个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()); 

这将是我使用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语句。 考虑到一个instanceof可以用Predicate替换

 Predicate isEngine = someObject -> someObject instanceof Engine; 

也可以表示为

 Predicate isEngine = Engine.class::isInstance 

同样,你的演员表可以用Function代替。

 Function castToEngine = someObject -> (Engine) someObject; 

这几乎是一样的

 Function castToEngine = Engine.class::cast; 

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

奖金挑战的解决方案

灵感来自@EdwinDalorzo的回答。

 public List list() { final List list = new ArrayList<>(); final StandardServer server = getServer(); return 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 -> mapContainers( Arrays.stream(host.findChildren()), host.getName()) ) .collect(Collectors.toList()); } private static Stream mapContainers(Stream containers, String hostname) { return containers .filter(Context.class::isInstance) .map(Context.class::cast) .map(context -> { ContextInfo info = new ContextInfo(context.getPath()); info.setThisPart(context.getThisPart()); info.setNotImportant(context.getNotImportant()); info.setHostname(hostname); // The Bonus Challenge return info; }); } 

第一次尝试超越难看。 我发现这可读性还需要几年时间。 必须是一个更好的方式。

请注意, findChildren方法返回的数组当然可以使用for (N n: array)语法,但不能使用新的Iterable.forEach方法。 不得不用Arrays.asList包装它们

 public List list() { final List list = new ArrayList(); final StandardServer server = getServer(); asList(server.findServices()).forEach(service -> { if (!(service.getContainer() instanceof Engine)) return; final Engine engine = (Engine) service.getContainer(); instanceOf(Host.class, asList(engine.findChildren())).forEach(host -> { instanceOf(Context.class, asList(host.findChildren())).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; } 

实用方法

 public static  Iterable instanceOf(final Class type, final Collection collection) { final Iterator iterator = collection.iterator(); return () -> new SlambdaIterator<>(() -> { while (iterator.hasNext()) { final Object object = iterator.next(); if (object != null && type.isAssignableFrom(object.getClass())) { return (T) object; } } throw new NoSuchElementException(); }); } 

最后是一个Lambda-powerable实现的Iterable

 public static class SlambdaIterator implements Iterator { // Ya put your Lambdas in there public static interface Advancer { T advance() throws NoSuchElementException; } private final Advancer advancer; private T next; protected SlambdaIterator(final Advancer advancer) { this.advancer = advancer; } @Override public boolean hasNext() { if (next != null) return true; try { next = advancer.advance(); return next != null; } catch (final NoSuchElementException e) { return false; } } @Override public T next() { if (!hasNext()) throw new NoSuchElementException(); final T v = next; next = null; return v; } @Override public void remove() { throw new UnsupportedOperationException(); } } 

很多管道,毫无疑问是字节码的5倍。 一定是更好的方式。