Finding Elements in Collections in Groovy

1. Introduction

Groovy provides a substantial number of methods enhancing Java’s core capabilities.

In this tutorial, we’ll show how Groovy does this when checking for an element and finding it in several types of collections.

2. Test if Element is Present

First, we’ll focus on just testing if a given collection contains an element.

2.1. List

Java itself provides several ways of checking for an item in a list with java.util.List:

  • The contains method

  • The indexOf method

As Groovy is a Java-compatible language, we can safely use them.

Let’s take a look at an example:

@Test
void whenListContainsElement_thenCheckReturnsTrue() {
    def list = ['a', 'b', 'c']

    assertTrue(list.indexOf('a') > -1)
    assertTrue(list.contains('a'))
}

Apart from that, Groovy introduces the membership operator:

element in list

It’s one of many syntactic sugar operators provided by Groovy. With its help, we can simplify our code:

@Test
void whenListContainsElement_thenCheckWithMembershipOperatorReturnsTrue() {
    def list = ['a', 'b', 'c']

    assertTrue('a' in list)
}

2.2. Set

As with the previous example, we can use the java.util.Set#contains method and the in operator:

@Test
void whenSetContainsElement_thenCheckReturnsTrue() {
    def set = ['a', 'b', 'c'] as Set

    assertTrue(set.contains('a'))
    assertTrue('a' in set)
}

2.3. Map

In the case of a Map, we can check for either the key or value directly:

@Test
void whenMapContainsKeyElement_thenCheckReturnsTrue() {
    def map = [a: 'd', b: 'e', c: 'f']

    assertTrue(map.containsKey('a'))
    assertFalse(map.containsKey('e'))
    assertTrue(map.containsValue('e'))
}

Or use membership operator to find the matching key:

@Test
void whenMapContainsKeyElement_thenCheckByMembershipReturnsTrue() {
    def map = [a: 'd', b: 'e', c: 'f']

    assertTrue('a' in map)
    assertFalse('f' in map)
}

When used with maps, we should use the membership operator with care because this operator is a bit confusing to use with boolean values. Rather than testing for the presence of the key, the underlying mechanism retrieves the corresponding value from the map and just casts it to boolean:

@Test
void whenMapContainsFalseBooleanValues_thenCheckReturnsFalse() {
    def map = [a: true, b: false, c: null]

    assertTrue(map.containsKey('b'))
    assertTrue('a' in map)
    assertFalse('b' in map)
    assertFalse('c' in map)
}

As we might see in the above example, it’s also a bit hazardous to use with null values either for the same reason. Groovy casts both false and null to boolean false.

3. All Match and Any Match

In most cases, we deal with collections composed of more complex objects. In this section, we’ll show how to check if the given collection contains at least one matching element or if all elements match a given predicate.

Let’s start by defining a simple class that we’ll use throughout our examples:

class Person {
    private String firstname
    private String lastname
    private Integer age

    // constructor, getters and setters
}

3.1. List/Set

This time, we’ll use a simple list of Person objects:

private final personList = [
  new Person("Regina", "Fitzpatrick", 25),
  new Person("Abagail", "Ballard", 26),
  new Person("Lucian", "Walter", 30),
]

As we mentioned before, Groovy is a Java-compatible language, so let’s first create an example using the Stream API introduced by Java 8:

@Test
void givenListOfPerson_whenUsingStreamMatching_thenShouldEvaluateList() {
    assertTrue(personList.stream().anyMatch {it.age > 20})
    assertFalse(personList.stream().allMatch {it.age < 30})
}

We can also use the Groovy methods DefaultGroovyMethods#any and DefaultGroovyMethods#every that perform the check directly on the collection:

@Test
void givenListOfPerson_whenUsingCollectionMatching_thenShouldEvaluateList() {
    assertTrue(personList.any {it.age > 20})
    assertFalse(personList.every {it.age < 30})
}

3.2. Map

Let’s start by defining a Map of Person objects mapped by Person#firstname:

private final personMap = [
  Regina : new Person("Regina", "Fitzpatrick", 25),
  Abagail: new Person("Abagail", "Ballard", 26),
  Lucian : new Person("Lucian", "Walter", 30)
]

We can evaluate it by either its keys, values, or by whole entries. Again, let’s first use the Stream API:

@Test
void givenMapOfPerson_whenUsingStreamMatching_thenShouldEvaluateMap() {
    assertTrue(personMap.keySet().stream().anyMatch {it == "Regina"})
    assertFalse(personMap.keySet().stream().allMatch {it == "Albert"})
    assertFalse(personMap.values().stream().allMatch {it.age < 30})
    assertTrue(personMap.entrySet().stream().anyMatch
      {it.key == "Abagail" && it.value.lastname == "Ballard"})
}

And then, the Groovy Collection API:

@Test
void givenMapOfPerson_whenUsingCollectionMatching_thenShouldEvaluateMap() {
    assertTrue(personMap.keySet().any {it == "Regina"})
    assertFalse(personMap.keySet().every {it == "Albert"})
    assertFalse(personMap.values().every {it.age < 30})
    assertTrue(personMap.any {firstname, person -> firstname == "Abagail" && person.lastname == "Ballard"})
}

As we can see, Groovy not only adequately replaces the Stream API when manipulating maps but also allows us to perform a check directly on the Map object instead of using the java.util.Map#entrySet method.

4. Find One or More Elements in a Collection


==== 4.1. List/Set

We can also extract elements using predicates. Let’s start with the familiar Stream API approach:

@Test
void givenListOfPerson_whenUsingStreamFind_thenShouldReturnMatchingElements() {
    assertTrue(personList.stream().filter {it.age > 20}.findAny().isPresent())
    assertFalse(personList.stream().filter {it.age > 30}.findAny().isPresent())
    assertTrue(personList.stream().filter {it.age > 20}.findAll().size() == 3)
    assertTrue(personList.stream().filter {it.age > 30}.findAll().isEmpty())
}

As we can see, the above example uses java.util.Optional for finding a single element as the Stream API forces that approach.

On the other hand, Groovy offers a much more compact syntax:

@Test
void givenListOfPerson_whenUsingCollectionFind_thenShouldReturnMatchingElements() {
    assertNotNull(personList.find {it.age > 20})
    assertNull(personList.find {it.age > 30})
    assertTrue(personList.findAll {it.age > 20}.size() == 3)
    assertTrue(personList.findAll {it.age > 30}.isEmpty())
}

By using Groovy’s API, we can skip creating a Stream and filtering it.

4.2. Map

In the case of a Map, there are several options to choose from. We can find elements amongst keys, values or complete entries. As the first two are basically a List or a Set, in this section we’ll only show an example of finding entries.

Let’s reuse our personMap from earlier:

@Test
void givenMapOfPerson_whenUsingStreamFind_thenShouldReturnElements() {
    assertTrue(
      personMap.entrySet().stream()
        .filter {it.key == "Abagail" && it.value.lastname == "Ballard"}
        .findAny().isPresent())
    assertTrue(
      personMap.entrySet().stream()
        .filter {it.value.age > 20}
        .findAll().size() == 3)
}

And again, the simplified Groovy solution:

@Test
void givenMapOfPerson_whenUsingCollectionFind_thenShouldReturnElements() {
    assertNotNull(personMap.find {it.key == "Abagail" && it.value.lastname == "Ballard"})
    assertTrue(personMap.findAll {it.value.age > 20}.size() == 3)
}

In this case, the benefits are even more significant. We skip the java.util.Map#entrySet method and use a closure with a function provided on the Map.

5. Conclusion

In this article, we presented how Groovy simplifies checking for elements and finding them in several types of collections.

As always, the complete code examples used in this tutorial are available over on GitHub.

Leave a Reply

Your email address will not be published.