使用Java 8流在两个列表中查找匹配元素

12

我的情况是:

class Person {
    String id ;
    String name;
    String age;
}
List<Person> list1 = {p1,p2, p3};
List<Person> list2 = {p4,p5, p6}; 

我想知道在list1中是否有人与list2中的人拥有相同的姓名和年龄,但是不考虑id

有什么最好和最快的方法吗?


在我的情况下,等号无法被重载。 - TNN
8个回答

14

定义一个键对象来保存和比较所需的属性。在这个简单的例子中,你可以使用一个小列表,其中每个索引对应一个属性。对于更复杂的情况,你可以使用一个Map(以属性名称作为键)或一个专用类:

Function<Person,List<Object>> toKey=p -> Arrays.asList(p.getName(), p.getAge());

有了这样的映射函数,你可以使用以下简单解决方案:

list1.stream().map(toKey)
     .flatMap(key -> list2.stream().map(toKey).filter(key::equals))
     .forEach(key -> System.out.println("{name="+key.get(0)+", age="+key.get(1)+"}"));

当你有相当大的列表时,使用 Set 作为中间步骤可以加速查找,避免低效率(时间复杂度从 O(n²) 变为 O(n))。

list2.stream().map(toKey)
     .filter(list1.stream().map(toKey).collect(Collectors.toSet())::contains)
     .forEach(key -> System.out.println("{name="+key.get(0)+", age="+key.get(1)+"}"));
在上面的示例中,每个匹配都被打印出来。如果你只关心是否存在这样的匹配,你可以使用以下任意一种方法:
boolean exists=list1.stream().map(toKey)
     .anyMatch(key -> list2.stream().map(toKey).anyMatch(key::equals));
或者
boolean exists=list2.stream().map(toKey)
     .anyMatch(list1.stream().map(toKey).collect(Collectors.toSet())::contains);

我只是想知道,每次调用flatMap时创建新集合是否会有性能问题? - thang
2
@thang 你是指“过滤”操作吗?当你使用形式为expression::name的方法引用时,expression只会被评估一次,在创建使用评估结果的函数之前。只有name的调用会重复。 - Holger

7

一个简单的方法是重写 equalshashCode 方法。因为我假设在比较 Person 实例时也要考虑 id 字段,所以你可以将这个实例封装到一个 PersonWrapper 中,这个类将会实现正确的 equalshashCode(只检查 nameage 字段):

class PersonWrapper {

    private Person person;

    private PersonWrapper(Person person) {
        this.person = person;
    }

    public static PersonWrapper wrap(Person person) {
        return new PersonWrapper(person);
    }

    public Person unwrap() {
        return person;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }
        PersonWrapper other = (PersonWrapper) obj;
        return person.name.equals(other.person.name) && person.age.equals(other.person.age);
    }

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

}

有了这样一个类,你可以实现以下功能:

Set<PersonWrapper> set2 = list2.stream().map(PersonWrapper::wrap).collect(toSet());

boolean exists =
    list1.stream()
         .map(PersonWrapper::wrap)
         .filter(set2::contains)
         .findFirst()
         .isPresent();

System.out.println(exists);

这段代码将list2转换为包装人员的Set。使用Set的目的是为了获得更好的性能,使contains操作的时间复杂度为常数级别。
然后,对list1进行过滤。保留在set2中发现的每个元素,如果还有剩余元素(也就是说,如果findFirst()返回一个非空的Optional),则表示找到了一个元素。

在我的情况下,等号不能被覆盖。 - TNN
4
我猜测您已经这样做了,并创建了一个自定义类并实现了equals方法,详见我的答案。 - Tunaki

6

这是一种基于 Java 8 的暴力破解方法,但代码十分纯粹:

boolean present = list1
        .stream()
        .flatMap(x -> list2
            .stream()
            .filter(y -> x.getName().equals(y.getName()))
            .filter(y -> x.getAge().equals(y.getAge()))
            .limit(1))
        .findFirst()
        .isPresent();

在这里,flatmap 用于连接两个列表。使用 limit 是因为我们只对第一个匹配感兴趣,在这种情况下,我们不需要进一步遍历。

3
<h3>Find List of Object passing String of Array Using java 8?</h3>
[Faiz Akram][1]
    <pre>
    public class Student {
        private String name;
        private Integer age;
        public Student(String name, Integer age) {
            super();
            this.name = name;
            this.age = age;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public Integer getAge() {
            return age;
        }
        public void setAge(Integer age) {
            this.age = age;
        }
    }
    </pre>
    // Main Class
    <pre>
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.stream.Collectors;
    public class JavaLamda {
        public static void main(String[] k)
        {
        List<Student> stud = new ArrayList<Student>();  
        stud.add(new Student("Faiz", 1));
        stud.add(new Student("Dubai", 2));
        stud.add(new Student("Akram", 5));
        stud.add(new Student("Rahul", 3));
        String[] name= {"Faiz", "Akram"};
        List<Student> present = Arrays.asList(name)
                .stream()
                .flatMap(x -> stud
                    .stream()
                    .filter(y -> x.equalsIgnoreCase(y.getName())))
                .collect(Collectors.toList());
        System.out.println(present);
        }
    }
    </pre>
    OutPut //[Student@404b9385, Student@6d311334]


  [1]: http://faizakram.com/blog/find-list-object-passing-string-array-using-java-8/

3

如果你不关心id字段,那么你可以使用equals方法来解决这个问题。

下面是Person类的代码:

public class Person {
  private String id ;
  private String name;
  private String age;

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    Person sample = (Person) o;

    if (!name.equals(sample.name)) return false;
    return age.equals(sample.age);

  }

  @Override
  public int hashCode() {
    int result = name.hashCode();
    result = 31 * result + age.hashCode();
    return result;
  }
}

现在,您可以使用流来获取交集,像这样。 "common" 将包含所有 "Person" 对象,其中 "name" 和 "age" 相同。
List<Person> common = list1
      .stream()
      .filter(list2::contains)
      .collect(Collectors.toList());

在我的情况下,等于(equals)方法无法被重写。 - TNN

2
这将起作用:
class PresentOrNot { boolean isPresent = false; };
final PresentOrNot isPresent = new PresentOrNot ();
l1.stream().forEach(p -> {
    isPresent.isPresent = isPresent.isPresent || l2.stream()
        .filter(p1 -> p.name.equals(p1.name) && p.age.equals(p1.age))
        .findFirst()
        .isPresent();
});
System.err.println(isPresent.isPresent);

由于forEach()接受Consumer,我们无法返回值,因此PresentOrNot {}是一种解决方法。

附:你从哪里得到这样的要求的?:)


1
你需要迭代两个列表并比较属性。
for(Person person1 : list1) {
    for(Person person2 : list2) {
        if(person1.getName().equals(person2.getName()) && 
                person1.getAge().equals(person2.getAge())) {
            //your code
        }
    }
}

需要更好的Java8流方式 :) - TNN

0
public static void main(String[] args) {
    OTSQuestions ots = new OTSQuestions();

    List<Attr> attrs = ots.getAttrs();
    List<String> ids = new ArrayList<>();
    ids.add("101");
    ids.add("104");
    ids.add("102");

    List<Attr> finalList = attrs.stream().filter(
            attr -> ids.contains(attr.getId()))
            .collect(Collectors.toList());
}

public class Attr {
    private String id;
    private String name;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

private List<Attr> getAttrs() {
    List<Attr> attrs = new ArrayList<>();
    Attr attr = new Attr();
    attr.setId("100");
    attr.setName("Yoga");
    attrs.add(attr);

    Attr attr1 = new Attr();
    attr1.setId("101");
    attr1.setName("Yoga1");
    attrs.add(attr1);

    Attr attr2 = new Attr();
    attr2.setId("102");
    attr2.setName("Yoga2");
    attrs.add(attr2);

    Attr attr3 = new Attr();
    attr3.setId("103");
    attr3.setName("Yoga3");
    attrs.add(attr3);

    Attr attr4 = new Attr();
    attr4.setId("104");
    attr4.setName("Yoga4");
    attrs.add(attr4);

    return attrs;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接