【问题标题】:How to get a list output from forEach loop in Java 8 Streams如何从 Java 8 Streams 中的 forEach 循环中获取列表输出
【发布时间】:2018-05-31 06:34:42
【问题描述】:

我有两个不同的相同对象列表,但属性不同,并且在这些对象中有一个共同的标识符。我想遍历第一个列表并从第二个列表(具有公共属性)中获取相应的对象,然后将这些对象包装起来,最后使用 Java Streams 将该对象添加到列表中。

这是我举的例子。

private class Person {
        private String name;
        private boolean isSenior;

        private Person(String name, boolean isSenior) {
            this.name = name;
            this.isSenior = isSenior;
        }

        public String getName() {
            return name;
        }

        public boolean isSenior() {
            return isSenior;
        }

        @Override
        public String toString() {
            return name + ": " + isSenior;
        }
    }

    private class PersonWrapper {
        private Person jrPerson;
        private Person srPerson;

        private PersonWrapper(Person jrPerson, Person srPerson) {
            this.jrPerson = jrPerson;
            this.srPerson = srPerson;
        }

        public Person getJrPerson() {
            return jrPerson;
        }

        public Person getSrPerson() {
            return srPerson;
        }

        @Override
        public String toString() {
            return jrPerson.toString() + "-" + srPerson.toString();
        }
    }

现在,在主类中,我将创建两个这样的列表实例

List<Person> jrPersons = new ArrayList<>();
List<Person> srPersons = new ArrayList<>();

并按以下方式添加对象

jrList.add(new Person("John", false));
jrList.add(new Person("Paul", false));
jrList.add(new Person("Mike", false));

seniorList.add(new Person("John", true));
seniorList.add(new Person("Paul", true));
seniorList.add(new Person("Mike", true));

现在,我想遍历 jrList 并在 srList 中找到对应的 Person 对象(同名)。然后我会将这些对象包装为 PersonWrapper 并将该对象包装到一个列表中。

到目前为止,这就是我一直在做的事情

List<PersonWrapper> wrapperList = new ArrayList<>();

jrList.forEach(jr -> seniorList.stream().filter(sr -> jr.getName().equals(sr.getName())).map(sr -> new PersonWrapper(jr, sr)).collect(Collectors.toList()));

现在,我想知道如何将Collectors.toList() 替换为wrapperList,或者如何将Collectors.toList() 的输出添加到wrapperList

请帮助我实现这一目标。

【问题讨论】:

    标签: java java-8 java-stream


    【解决方案1】:

    不要使用forEach,而是从头开始使用流:

    List<PersonWrapper> wrapperList = jrList.stream()
        .flatMap(jr -> seniorList.stream()
             .filter(sr -> jr.getName().equals(sr.getName()))
             .map(sr -> new PersonWrapper(jr, sr))
        )
        .collect(Collectors.toList());
    

    通过使用flatMap,您可以展平一个流 (Stream&lt;Stream&lt;PersonWrapper&gt;&gt;) 到单个流 (Stream&lt;PersonWrapper&gt;)

    如果您不能自己实例化wrapperList 或确实需要附加到它。您可以将上面的 sn-p 更改为以下内容:

    List<PersonWrapper> wrapperList = new ArrayList<>();
    
    jrList.stream()
        .flatMap(jr -> seniorList.stream()
             .filter(sr -> jr.getName().equals(sr.getName()))
             .map(sr -> new PersonWrapper(jr, sr))
        )
        .forEach(wrapperList::add);
    

    【讨论】:

    • 谢谢@Lino。这符合我的目的
    • 您能否解释一下 Stream> 是如何创建的?上面表达式中的哪个操作? @Lino
    • @Ravi 在第一个 sn-p 中,您在第一行创建了一个流。现在想象在第二行中,不要使用flatMap,而是使用map。在map 操作中,我们创建另一个流filter,然后map 将结果发送到新的PersonWrapper 实例。您永远不会终止流,您只是在添加操作。因此,您实际上是将Person 映射到Stream&lt;PersonWrapper&gt;,最终生成Stream&lt;Stream&lt;PersonWrapper&gt;&gt;
    • 谢谢@Lino。现在明白了。另外,我能知道为什么在map(sr -&gt; new PersonWrapper(jr, sr)) 之前添加findFirst().get() 会给我编译时错误,说“Stream”不能转换为Unknown”吗?
    • 嗯,我猜这是因为flatMap 期望返回Stream。但是通过使用findFirst().get(),你试图返回一个PersonalWrapper
    【解决方案2】:

    虽然Lino's 的答案肯定是正确的。我会争辩说,如果jrList 中的给定人员对象最多只能在seniorList 中有一个对应的匹配项,换句话说,如果它是1-1 关系,那么您可以改进Lino 给出的解决方案找到第一个匹配如下:

    List<PersonWrapper> resultSet = jrList.stream()
                    .map(p -> seniorList.stream()
                            .filter(sr -> p.getName().equals(sr.getName()))
                            .findFirst()
                            .map(q -> new PersonWrapper(p, q))
                            .get())
                    .collect(Collectors.toList());
    

    或者如果不能保证jrList 中的每个人在seniorList 中都有对应的匹配项,则将上述查询更改为:

    List<PersonWrapper> resultSet = jrList.stream()
                    .map(p -> seniorList.stream()
                            .filter(sr -> p.getName().equals(sr.getName()))
                            .findFirst()
                            .map(q -> new PersonWrapper(p, q))
                            .orElse(null))
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList());
    

    不同之处在于,现在不是在findFirst() 的结果上调用get(),而是提供orElse 的默认值,以防findFirst 找不到相应的值,然后我们filter null 值在后续的中间操作中退出,因为它们不需要。

    【讨论】:

    • 感谢您的新答案。我试图完成这个要求,但我做不到。现在,很清楚了。
    • 从 Java9 开始,您可能希望省略 orElse(),而只使用 .flatMap(p -&gt; seniorList.stream() .filter(sr -&gt; p.getName().equals(sr.getName())) .findFirst() .map(q -&gt; new PersonWrapper(p, q)).stream() 将可选内容扁平化为流
    • @Lino 好声音,在这种情况下,我们不再需要对空元素进行后续过滤。我不得不在这里限制自己,因为问题是用 java-8 标记的,因此没有提到任何关于 java-9 的内容。但是,是的,这是一个很好的呼喊。谢谢!
    【解决方案3】:

    用下面的代码替换你的循环逻辑。

     jrList.forEach(jr -> seniorList.stream().filter(sr -> jr.getName().equals(sr.getName()))
                        .map(sr -> wrapperList.add(new PersonWrapper(jr, sr))).collect(Collectors.toList()));
    

    【讨论】:

    • 不推荐在map 中使用副作用。作为终端操作,收集到列表也没有意义
    • 这听起来很有效。谢谢@Dharita。考虑到 Lino 的评论,我认为他的回答更有意义
    猜你喜欢
    • 2019-09-06
    • 1970-01-01
    • 2015-12-13
    • 2022-12-18
    • 2014-09-03
    • 2015-11-14
    • 2021-09-12
    • 1970-01-01
    • 2019-02-25
    相关资源
    最近更新 更多