java Lambdas,多个 forEach 与铸造

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25439277/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 08:02:48  来源:igfitidea点击:

Lambdas, multiple forEach with casting

javalambdajava-8java-stream

提问by David Blevins

Need some help thinking in lambdas from my fellow StackOverflow luminaries.

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

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 Lambdashelp with this boilerplate?

通过一个列表的一个列表挑选一个列表来收集一个图表深处的一些孩子的标准案例。什么很棒的方法可以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;
}

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.

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

Interested to see what my fellow experts create. Multiple approaches encouraged.

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

EDIT

编辑

The findServicesand the two findChildrenmethods return arrays

findServices和两个findChildren方法返回数组

EDIT - BONUS CHALLENGE

编辑 - 奖金挑战

The "not important part" did turn out to be important. I actually need to copy a value only available in the hostinstance. This seems to ruin all the beautiful examples. How would one carry state forward?

“不重要的部分”确实很重要。我实际上需要复制一个仅在host实例中可用的值。这似乎毁了所有美丽的例子。一个人将如何推进国家发展?

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

回答by Stuart Marks

It's fairly deeply nested but it doesn't seem exceptionally difficult.

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

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 Serviceso we turn this into a stream using Arrays.stream(). (I make similar assumptions for Engine.findChildren()and Host.findChildren().

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

Next, the logic within each loop does an instanceofcheck and a cast. This can be modeled using streams as a filteroperation to do the instanceoffollowed by a mapoperation 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.

接下来,每个循环中的逻辑进行instanceof检查和强制转换。这可以使用流作为一个filter操作来建模,instanceof然后是一个map简单的转换和返回相同引用的操作。这实际上是一个空操作,但它允许静态类型系统将 a 转换Stream<Container>为 a Stream<Host>

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;
}

But wait, there's more.

但是等等,还有更多。

The final forEachoperation is a slightly more complicated mapoperation that converts a Contextinto a ContextInfo. Furthermore, these are just collected into a Listso 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:

最后一个forEach操作是一个稍微复杂的map操作,将 a 转换Context为 a ContextInfo。此外,这些只是收集到 a 中,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());
}

I usually try to avoid multi-line lambdas (such as in the final mapoperation) so I'd refactor it into a little helper method that takes a Contextand returns a ContextInfo. This doesn't shorten the code at all, but I think it does make it clearer.

我通常会尽量避免多行 lambdas(例如在最终map操作中),因此我会将其重构为一个小辅助方法,该方法接受 aContext并返回 a ContextInfo。这根本没有缩短代码,但我认为它确实使它更清晰。

UPDATE

更新

But wait, there's still more.

但是等等,还有更多。

Let's extract the call to service.getContainer()into its own pipeline element:

让我们将调用提取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()))
        // ...

This exposes the repetition of filtering on instanceoffollowed 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 filtercan change the number of elements in the stream (dropping ones that don't match) but it can't change their types. And mapcan 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 flatMapagain! 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:

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

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

(This is loosely based on C#'s OfTypeconstruct mentioned in some of the comments.)

(这松散地基于OfType某些评论中提到的C#构造。)

While we're at it, let's extract a method to create a ContextInfo:

在此过程中,让我们提取一个方法来创建一个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());

Nicer, I think, and we've removed the dreaded multi-line statement lambda.

我认为更好,而且我们已经删除了可怕的多行语句 lambda。

UPDATE: BONUS CHALLENGE

更新:奖励挑战

Once again, flatMapis your friend. Take the tail of the stream and migrate it into the last flatMapbefore the tail. That way the hostvariable is still in scope, and you can pass it to a makeContextInfohelper method that's been modified to take hostas well.

再一次,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());

回答by Edwin Dalorzo

This would be my version of your code using JDK 8 streams, method references, and lambda expressions:

这将是我使用 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());

In this approach, I replace your if-statements for filter predicates. Take into account that an instanceofcheck can be replaced with a Predicate<T>

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

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

which can also be expressed as

也可以表示为

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

Similarly, your casts can be replaced by Function<T,R>.

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

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

Which is pretty much the same as

这与

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

And adding items manually to a list in the for loop can be replaced with a collector. In production code, the lambda that transforms a Contextinto a ContextInfocan (and should) be extracted into a separate method, and used as a method reference.

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

回答by user11153

Solution to bonus challenge

奖金挑战的解决方案

Inspired by @EdwinDalorzo answer.

受到@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;
            });
}

回答by David Blevins

First attempt beyond ugly. It will be years before I find this readable. Has to be a better way.

第一次尝试超越丑陋。我要过几年才能找到这种可读性。必须有更好的方法。

Note the findChildrenmethods return arrays which of course work with for (N n: array)syntax, but not with the new Iterable.forEachmethod. Had to wrap them with Arrays.asList

请注意,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;
}

The utility methods

实用方法

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

And finally a Lambda-powerable implementation of Iterable

最后是一个由 Lambda 驱动的实现 Iterable

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

Lots of plumbing and no doubt 5x the byte code. Must be a better way.

大量的管道,毫无疑问是字节码的 5 倍。一定有更好的办法。