Groovy intersect lists with objects

By , last updated December 20, 2019

In this post we will show how to find common objects in list collections by using an intersect method in Groovy.

We will also show how to remove common objects from two lists in Groovy by using a removeAll method.

Both operations will require an algorithm to compare complex objects. We will show how to do it in Java and Groovy with Grails domain objects.

Intersecting simple objects example

With simple objects like Strings the intersection of lists works right out of the box.

Here is a simple Groovy unit test example showing how to find common String objects within two collections:

void "should find Foo"() {
        setup:
        ArrayList list1 = new ArrayList()
        list1.add("Foo")
        list1.add("Bar")

        ArrayList list2 = new ArrayList<>()
        list2.add("Foo")
        list2.add("Gob")

        expect:
        list1.intersect(list2).size() == 1
    }

Intersecting complex objects

For the purpose of testing intersection of complex objects we will create a domain class Employee. Each employee will have an id, name, role and a phone number:

@EqualsAndHashCode
class Employee implements Comparable {

    String id
    String username
    String role
    String phone

    @Override
    int compareTo(Object o) {
        int result = workerId <=> o.workerId ?:
                username <=> o.username ?:
                        role <=> o.role

        result
    }
}

In order for the intersect method to work on objects, you MUST provide a comparison method. See How to implement a Comparable interface in domain objects in Grails for more details.

We will create two lists of employees and find common employees in both lists. Here is a Groovy code that will find an intersection between to collections of employees:

 def findCommon(ArrayList list1, ArrayList list2) {
        list1.intersect(list2)
    }

Here’s the groovy test code example. We created two lists where employee2 is the common object.

 void "should find 1 common employee"() {
        setup:
        def employee1 = new Employee(id: '1', username: 'employee1', role: 'leader')
        def employee2 = new Employee(id: '2', username: 'employee2', role: 'worker')
        def employee3 = new Employee(id: '3', username: 'employee3', role: 'worker')

        ArrayList list1 = new ArrayList()
        list1.add(employee1)
        list1.add(employee2)

        ArrayList list2 = new ArrayList<>()
        list2.add(employee2)
        list2.add(employee3)

        expect:
        findCommon(list1, list2).size() == 1

    }

Removing common objects from lists

Now we will take the above example a little bit further and remove the common objects from both lists. The result will be 2 lists with unique objects.

For this we will use the Groovy method removeAll.

In order for the removeAll method to work, we need to implement Equals and HashCode methods in our domain objects.

Refer to the Recipe for a high quality equals method post for details on how to implement the methods.

In Groovy, however, we can also use the @EqualsAndHashCode annotation instead of implementing the methods by hand. This will work for our simple case of an Employee object.

void "should remove 1 common employee"() {
    setup:
    def employee1 = new Employee(id: '1', username: 'employee1', role: 'leader')
    def employee2 = new Employee(id: '2', username: 'employee2', role: 'worker')
    def employee3 = new Employee(id: '3', username: 'employee3', role: 'worker')

    ArrayList list1 = new ArrayList()
    list1.add(employee1)
    list1.add(employee2)

    ArrayList list2 = new ArrayList<>()
    list2.add(employee2)
    list2.add(employee3)

    def commons = findCommon(list1, list2)

    list1.removeAll(commons)
    list2.removeAll(commons)

    expect:
    list1.size() == 1
    list2.size() == 1
}