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

5 java lambda arraylist java-8 java-stream

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

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

我想通过name和比较这两个列表中的对象school。如果它们相等,我需要创建一个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;
    }
}
Run Code Online (Sandbox Code Playgroud)

Avi*_*Avi 11

让我们来看看代码的每一部分。首先,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;
}
Run Code Online (Sandbox Code Playgroud)

运行完代码后,它会完全按照您的意愿执行操作。现在,让我们来看看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;
}
Run Code Online (Sandbox Code Playgroud)

到目前为止,很好......对吧?事实上,你的代码从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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

List<SchoolObj> result = ...;
result = result.stream().distinct().collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

但是,它在以下场景中不起作用,除非您覆盖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
Run Code Online (Sandbox Code Playgroud)


Ekl*_*vya 9

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

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

您需要重写类equals()中的方法SchoolObjcontains()方法 您将使用该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);
}
Run Code Online (Sandbox Code Playgroud)

但更好的解决方案是对一个列表使用 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());
Run Code Online (Sandbox Code Playgroud)

hashCode()您还需要equals()SchoolObj类中重写方法 for Set#contains。(假设nameandschool不能为空)

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

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