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

新手上路,请多包涵

我有两个 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;
    }
}

原文由 user10206883 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 653
1 个回答

让我们遍历代码的每个部分。首先, 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 将收集正确的对象,但它会收集两次。根据与 --- 的输出比较,我怀疑这是 createSharedListViaStream 输出“不正确”的 createSharedListViaLoop

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

但是,它不会在以下场景中工作,除非您重写 SchoolObj 的 equals 方法:

 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

原文由 Avi 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题