【问题标题】:Java 8 Streams - Compare two Lists' object values and add value to new List?Java 8 Streams - 比较两个列表的对象值并将值添加到新列表?
【发布时间】:2019-12-06 17:09:03
【问题描述】:

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

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

【问题讨论】:

  • 显示,不告诉。也许您的解决方案只需要更改 1 个字符。
  • 所做的更改,请重新检查我的问题@Michael
  • @Vinay 我将你的两个实现(循环方法和流方法)移动到一个带有 main 方法的类中,以便可以比较结果。如果您可以添加一些示例数据(请参阅代码中的 TODO 项),我们将有一个 SSCCE,这通常会导致该站点上的更多响应。

标签: java arraylist lambda java-8 java-stream


【解决方案1】:

如果包含在另一个列表中,您可以在一个列表中过滤然后收集。

List<SchoolObj> listCommon = listTwo.stream()
                                         .filter(e -> listOne.contains(e)) 
                                         .collect(Collectors.toList());

您需要覆盖SchoolObj 类中的equals() 方法。 contains() 方法你将使用equals() 方法来评估两个对象是否相同。

@Override
public boolean equals(Object o) {
    if (!(o instanceof SchoolObj))
        return false;
    SchoolObj n = (SchoolObj) o;
    return n.name.equals(name) && n.school.equals(school);
}

但更好的解决方案是对一个列表使用 Set 并在另一个列表中过滤以收集是否包含在 Set 中。 Set#contains 采用 O(1) 更快。

Set<SchoolObj> setOne = new HashSet<>(listOne);
List<SchoolObj> listCommon = listTwo.stream()
                                     .filter(e -> setOne.contains(e)) 
                                     .collect(Collectors.toList());

您还需要在SchoolObj 类中为Set#contains 覆盖hashCode() 方法以及equals()。(假设nameschool 不能为空)

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + name.hashCode();
    result = prime * result + school.hashCode();
    return result;
}

Here 您将详细了解如何以更好的方式覆盖 equals 和 hashCode

【讨论】:

    【解决方案2】:

    让我们遍历代码的每个部分。一、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
    

    但是,它在以下场景中不起作用,除非您覆盖 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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-01
      • 2017-01-12
      • 1970-01-01
      • 2020-04-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多