Java 8 Streams - 比较两个列表的对象值并将值添加到新列表?

标签 java arraylist lambda java-8 java-stream

我有两个包含此类对象的List:

public class SchoolObj
{
    private String name;
    private String school;

    public SchoolObj()
    {
        this(null, null);
    }

    public SchoolObj(String nameStr, String schoolStr)
    {
        this.setName(nameStr);
        this.setSchool(schoolStr);
    }

    public String getName()
    {
        return this.name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public String getSchool()
    {
        return this.school;
    }

    public void setSchool(String school)
    {
        this.school = school;
    }

    @Override
    public String toString()
    {
        return this.getName() + ' ' + this.getSchool();
    }
}

我想按 nameschool 比较这两个列表中的对象。如果它们相等,我需要创建一个新的 List ,其中包含在两个列表中找到的 SchoolObj 对象。

我知道我们可以使用两个 for 循环,并在下面的 createSharedListViaLoop 方法中执行此操作。

我的问题是,如何使用 Java 流完成同样的事情?

我尝试使用下面的createSharedListViaStream,但它没有按预期工作。

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class StreamTest
{
    public static void main(String[] args)
    {
        List<SchoolObj> listOne = new ArrayList<SchoolObj>();
        // TODO: Add sample data to listOne.
        listOne.add(new SchoolObj("nameA", "schoolX"));
        listOne.add(new SchoolObj("nameC", "schoolZ"));

        List<SchoolObj> listTwo = new ArrayList<SchoolObj>();
        // TODO: Add sample data to listTwo.
        listTwo.add(new SchoolObj("nameA", "schoolX"));
        listTwo.add(new SchoolObj("nameB", "schoolY"));

        // Print results from loop method.
        System.out.println("Results from loop method:");
        List<SchoolObj> resultsViaLoop = StreamTest.createSharedListViaLoop(listOne, listTwo);
        for (SchoolObj obj : resultsViaLoop)
        {
            System.out.println(obj);
        }

        // Print results from stream method.
        System.out.println("Results from stream method:");
        List<SchoolObj> resultsViaStream = StreamTest.createSharedListViaStream(listOne, listTwo);
        for (SchoolObj obj : resultsViaStream)
        {
            System.out.println(obj);
        }
    }

    public static List<SchoolObj> createSharedListViaLoop(List<SchoolObj> listOne, List<SchoolObj> listTwo)
    {
        List<SchoolObj> result = new ArrayList<SchoolObj>();

        for (SchoolObj one : listOne)
        {
            for (SchoolObj two : listTwo)
            {
                if (one.getName().equals(two.getName()) && one.getSchool().equals(two.getSchool()))
                {
                    result.add(one);
                }
            }
        }

        return result;
    }

    public static List<SchoolObj> createSharedListViaStream(List<SchoolObj> listOne, List<SchoolObj> listTwo)
    {
        List<SchoolObj> listOneList = listOne.stream().filter(two -> listTwo.stream()
              .anyMatch(one -> one.getName().equals(two.getName()) && two.getSchool().equals(one.getSchool()))) 
              .collect(Collectors.toList());

        return listOneList;
    }
}

最佳答案

让我们运行一下代码的每个部分。首先,createSharedListViaStream:

public static List<SchoolObj> createSharedListViaStream(List<SchoolObj> listOne, List<SchoolObj> listTwo)
{
    // We create a stream of elements from the first list.
    List<SchoolObj> listOneList = listOne.stream()
    // We select any elements such that in the stream of elements from the second list
    .filter(two -> listTwo.stream()
    // there is an element that has the same name and school as this element,
        .anyMatch(one -> one.getName().equals(two.getName()) 
            && two.getSchool().equals(one.getSchool())))
    // and collect all matching elements from the first list into a new list.
    .collect(Collectors.toList());
    // We return the collected list.
    return listOneList;
}

运行完代码后,它会完全按照您想要的方式执行。现在,让我们运行一下 createSharedListViaLoop:

public static List<SchoolObj> createSharedListViaLoop(List<SchoolObj> listOne, List<SchoolObj> listTwo)
{
    // We build up a result by...
    List<SchoolObj> result = new ArrayList<SchoolObj>();
    // going through each element in the first list,
    for (SchoolObj one : listOne)
    {
    // going through each element in the second list,
        for (SchoolObj two : listTwo)
        {
    // and collecting the first list's element if it matches the second list's element.
            if (one.getName().equals(two.getName()) && one.getSchool().equals(two.getSchool()))
            {
                result.add(one);
            }
        }
    }
    // We return the collected list
    return result;
}

到目前为止,一切都很好......对吧?事实上,您在 createSharedListViaStream 中的代码基本上是正确的;相反,您的 createSharedListViaLoop 可能会导致输出差异。

考虑以下一组输入:
List1 = [SchoolObj("nameA","SchoolX"), SchoolObj("nameC","SchoolZ")]
List2 = [SchoolObj("nameA","SchoolX"), SchoolObj("nameA","SchoolX"), SchoolObj("nameB","SchoolY")]

这里,createSharedListViaStream 将返回第一个列表中出现在两个列表中的唯一元素:SchoolObj("nameA","SchoolX")。但是,createSharedListViaLoop 将返回以下列表:[SchoolObj("nameA","SchoolX"),SchoolObj("nameA","SchoolX")]。更准确地说,createSharedListViaLoop 将收集正确的对象,但它会这样做两次。根据与 createSharedListViaLoop 的输出的比较,我怀疑这是 createSharedListViaStream 的输出“不正确”的原因。

createSharedListViaLoop 执行此重复操作的原因是其内部 for 循环没有终止。尽管我们迭代第一个列表的所有元素以检查它们是否存在于第二个列表中,但找到单个匹配项就足以将该元素添加到结果中。我们可以通过将内部循环更改为以下内容来避免冗余元素添加:

for (SchoolObj one : listOne)
    {
    for (SchoolObj two : listTwo)
    {
        if (one.getName().equals(two.getName()) && one.getSchool().equals(two.getSchool()))
        {
            result.add(one);
            break;
        }
    }
}

此外,如果您不想在列表中出现重复的对象(按内存中的位置),则可以使用 distinct像这样:

List<SchoolObj> result = ...;
result = result.stream().distinct().collect(Collectors.toList());

作为最后的警告,上述内容将在以下情况下保持不同的结果:

List<SchoolObj> list = new ArrayList<>();
SchoolObj duplicate = new SchoolObj("nameC", "schoolD");
listOne.add(duplicate);
listOne.add(duplicate);
list.stream().distinct().forEach(System.out::println); 
// prints:
// nameC schoolD

但是,在以下情况下它将不起作用,除非您覆盖 equals SchoolObj 的方法:

List<SchoolObj> list = new ArrayList<>();
listOne.add(new SchoolObj("nameC", "schoolD"));
listOne.add(new SchoolObj("nameC", "schoolD"));
list.stream().distinct().forEach(System.out::println); 
// prints (unless Object::equals overridden)
// nameC schoolD
// nameC schoolD

关于Java 8 Streams - 比较两个列表的对象值并将值添加到新列表?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57252497/

相关文章:

python - Pandas Groupby Lambda函数多个条件/列

java - 服务器仅获取空值java

java - 匿名内部类内部方法

java - 非法文本 block 打开定界符序列,缺少行终止符

Java GUI 使用 ArrayList 填充组合框值

php - 从表中检索特定行并将每一行存储为 session 中的关联数组

c++ - 如何传递带有泛型参数作为参数的 lambda 函数?

java - 使用 java8 流构建集合

Java - ByteBuffer 还是 ArrayList<Byte>?

c++ - 推导 C++11 中 lambda 参数的类型