Lambdas,多个forEach与铸造

时间:2014-08-22 03:52:57

标签: 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;
}

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

有兴趣了解我的同事专家创建的内容。鼓励采用多种方法。

修改

findServices和两个findChildren方法返回数组

编辑 - 奖金挑战

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

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

4 个答案:

答案 0 :(得分:36)

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

第一个观察结果是,如果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并返回{{1 }}。这根本不会缩短代码,但我认为它确实使代码更加清晰。

<强>更新

但是等等,还有更多。

让我们将对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了!所以我们的helper方法需要获取一个元素并返回一个不同类型的元素流。该返回流将包含单个转换元素(如果匹配)或者它将为空(如果它不匹配)。辅助函数如下所示:

flatMap

(这是基于一些评论中提到的C#的<T,U> Stream<U> toType(T t, Class<U> clazz) { if (clazz.isInstance(t)) { return Stream.of(clazz.cast(t)); } else { return Stream.empty(); } } 结构。)

虽然我们正在使用它,但我们提取一个方法来创建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;
}

好吧,我想,我们已经删除了可怕的多行语句lambda。

更新:奖金挑战

再一次, 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()); 是你的朋友。抓住流的尾部并将其迁移到尾部前的最后flatMap。这样flatMap变量仍在范围内,您可以将其传递给host辅助方法,该方法已被修改为也可以makeContextInfo

host

答案 1 :(得分: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());

在这种方法中,我替换了过滤谓词的if语句。考虑到instanceof支票可以替换为Predicate<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循环中的列表可以用收集器替换。在生产代码中,将Context转换为ContextInfo的lambda可以(并且应该)提取到单独的方法中,并用作方法参考。

答案 2 :(得分:2)

奖金挑战的解决方案

受@EdwinDalorzo的启发回答。

public List<ContextInfo> list() {
    final List<ContextInfo> 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<ContextInfo> mapContainers(Stream<Container> 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;
            });
}

答案 3 :(得分:1)

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

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

包装它们
public List<ContextInfo> list() {
    final List<ContextInfo> list = new ArrayList<ContextInfo>();
    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 <T> Iterable<T> instanceOf(final Class<T> 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();
    });
}

最后是Iterable

的Lambda-powerable实现
public static class SlambdaIterator<T> implements Iterator<T> {
    // Ya put your Lambdas in there
    public static interface Advancer<T> {
        T advance() throws NoSuchElementException;
    }
    private final Advancer<T> advancer;
    private T next;

    protected SlambdaIterator(final Advancer<T> 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倍。必须是更好的方式。