Find element matching in 2 lists using java 8 stream
Brute force, but pure java 8 solution will be this:
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();
Here, flatmap is used to join 2 lists. limit
is used as we are interested in first match only, in which case, we do not need to traverse further.
A simple way to do that is to override equals
and hashCode
. Since I assume the equality between Person
must also consider the id
field, you can wrap this instance into a PersonWrapper
which will implement the correct equals
and hashCode
(i.e. only check the name
and age
fields):
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;
}
}
With such a class, you can then have the following:
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);
This code converts the list2
into a Set
of wrapped persons. The goal of having a Set
is to have a constant-time contains
operation for better performance.
Then, the list1
is filtered. Every element found in set2
is kept and if there is an element left (that is to say, if findFirst()
returns a non empty Optional
), it means an element was found.
Well if you don't care about the id
field, then you can use the equals
method to solve this.
Here's the Person
class code
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;
}
}
Now, you can use stream to get the intersection like so. common
will contain all Person
objects where name
and age
are the same.
List<Person> common = list1
.stream()
.filter(list2::contains)
.collect(Collectors.toList());
Define yourself a key object that holds and compares the desired properties. In this simple case, you may use a small list whereas each index corresponds to one property. For more complex cases, you may use a Map
(using property names as keys) or a dedicated class:
Function<Person,List<Object>> toKey=p -> Arrays.asList(p.getName(), p.getAge());
Having such a mapping function. you may use the simple solution:
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)+"}"));
which may lead to poor performance when you have rather large lists. When you have large lists (or can’t predict their sizes), you should use an intermediate Set
to accelerate the lookup (changing the task’s time complexity from O(n²)
to 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)+"}"));
In the examples above, each match gets printed. If you are only interested in whether such a match exists, you may use either:
boolean exists=list1.stream().map(toKey)
.anyMatch(key -> list2.stream().map(toKey).anyMatch(key::equals));
or
boolean exists=list2.stream().map(toKey)
.anyMatch(list1.stream().map(toKey).collect(Collectors.toSet())::contains);