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