使用java 8 stream在2个列表中查找元素匹配

TNN*_*TNN 9 java java-8 java-stream

我的情况是:

class Person {
    String id ;
    String name;
    String age;
}
List<Person> list1 = {p1,p2, p3};
List<Person> list2 = {p4,p5, p6}; 
Run Code Online (Sandbox Code Playgroud)

我想知道是否有人list1有相同的名字和年龄,list2但不介意id.

什么是最好和最快的方式?

Tun*_*aki 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;
    }

}
Run Code Online (Sandbox Code Playgroud)

有了这样的课程,您就可以拥有以下内容:

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

此代码将其list2转换Set为包装人员.拥有a的目标Set是进行恒定时间contains操作以获得更好的性能.

然后,list1过滤.找到的每个元素都set2被保留,如果剩下一个元素(也就是说,如果findFirst()返回非空Optional元素),则表示找到了一个元素.

  • @TodorNeykov这就是我猜的,我用`equals`实现了一个自定义类,看看我的回答. (4认同)

Hol*_*ger 7

为自己定义一个关键对象,该对象可以保存并比较所需的属性。在这种简单情况下,您可以使用一个小的列表,而每个索引对应一个属性。对于更复杂的情况,可以使用Map(使用属性名称作为键)或专用类:

Function<Person,List<Object>> toKey=p -> Arrays.asList(p.getName(), p.getAge());
Run Code Online (Sandbox Code Playgroud)

具有这种映射功能。您可以使用简单的解决方案:

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

如果您的列表很大,可能会导致性能不佳。如果列表较大(或无法预测其大小),则应使用中间变量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)+"}"));
Run Code Online (Sandbox Code Playgroud)

在上面的示例中,每个匹配项都被打印出来。如果您仅对是否存在这样的匹配感兴趣,则可以使用以下任一方法:

boolean exists=list1.stream().map(toKey)
     .anyMatch(key -> list2.stream().map(toKey).anyMatch(key::equals));
Run Code Online (Sandbox Code Playgroud)

要么

boolean exists=list2.stream().map(toKey)
     .anyMatch(list1.stream().map(toKey).collect(Collectors.toSet())::contains);
Run Code Online (Sandbox Code Playgroud)

  • @thang你的意思是“过滤器”操作吗?当您使用“表达式::名称”形式的方法引用时,在创建使用计算结果的函数之前,“表达式”只会计算一次。仅会重复调用“name”。 (2认同)

Mri*_*nal 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();
Run Code Online (Sandbox Code Playgroud)

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