1. Overview

In this tutorial, we’ll illustrate how to filter and transform collections with Guava.

We will filter using Predicates, transform using the Functions that the library provides and finally, we’ll see how to combine both filtering and transforming.

2. Filter a Collection

Let’s start with a simple example of filtering a collection. We’ll be using an out of the box Predicate provided by the library and constructed via the Predicates utility class:

@Test
public void whenFilterWithIterables_thenFiltered() {
    List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Iterable<String> result 
      = Iterables.filter(names, Predicates.containsPattern("a"));

    assertThat(result, containsInAnyOrder("Jane", "Adam"));
}

As you can see, we’re filtering the List of names to get only the names which contain the character “a” – and we’re using Iterables.filter() to do it.

Alternatively, we can make good use of Collections2.filter() API as well:

@Test
public void whenFilterWithCollections2_thenFiltered() {
    List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Collection<String> result 
      = Collections2.filter(names, Predicates.containsPattern("a"));
    
    assertEquals(2, result.size());
    assertThat(result, containsInAnyOrder("Jane", "Adam"));

    result.add("anna");
    assertEquals(5, names.size());
}

A few things to note here – first, the output of Collections.filter() is a live view of the original collection – changes to one will be reflected in the other.

It’s also important to understand that now, the result is constrained by the predicate – if we add an element that doesn’t satisfy that Predicate, an IllegalArgumentException will be thrown:

@Test(expected = IllegalArgumentException.class)
public void givenFilteredCollection_whenAddingInvalidElement_thenException() {
    List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Collection<String> result 
      = Collections2.filter(names, Predicates.containsPattern("a"));

    result.add("elvis");
}

3. Write Custom Filter Predicate

Next – let’s write our own Predicate instead of using one provided by the library. In the following example – we’ll define a predicate that only gets the names that start with “A” or “J”:

@Test
public void whenFilterCollectionWithCustomPredicate_thenFiltered() {
    Predicate<String> predicate = new Predicate<String>() {
        @Override
        public boolean apply(String input) {
            return input.startsWith("A") || input.startsWith("J");
        }
    };

    List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Collection<String> result = Collections2.filter(names, predicate);

    assertEquals(3, result.size());
    assertThat(result, containsInAnyOrder("John", "Jane", "Adam"));
}

4. Combine Multiple Predicates

We can Combine Multiple Predicates using Predicates.or() and Predicates.and().
In the following example – we filter a List of names to get the names that start with “J” or don’t contain “a”:

@Test
public void whenFilterUsingMultiplePredicates_thenFiltered() {
    List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Collection<String> result = Collections2.filter(names, 
      Predicates.or(Predicates.containsPattern("J"), 
      Predicates.not(Predicates.containsPattern("a"))));

    assertEquals(3, result.size());
    assertThat(result, containsInAnyOrder("John", "Jane", "Tom"));
}

5. Remove Null Values While Filtering a Collection

We can clean up the null values from a collection by filtering it with Predicates.notNull() as in the following example:

@Test
public void whenRemoveNullFromCollection_thenRemoved() {
    List<String> names = 
      Lists.newArrayList("John", null, "Jane", null, "Adam", "Tom");
    Collection<String> result = 
      Collections2.filter(names, Predicates.notNull());

    assertEquals(4, result.size());
    assertThat(result, containsInAnyOrder("John", "Jane", "Adam", "Tom"));
}

6. Check If All Elements in a Collection Match a Condition

Next, let’s check if all elements in a Collection match a certain condition. We’ll use Iterables.all() to check if all names contain “n” or “m”, then we’ll check if all elements contain “a”:

@Test
public void whenCheckingIfAllElementsMatchACondition_thenCorrect() {
    List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");

    boolean result = Iterables.all(names, Predicates.containsPattern("n|m"));
    assertTrue(result);

    result = Iterables.all(names, Predicates.containsPattern("a"));
    assertFalse(result);
}

7. Transform a Collection

Now – let’s see how to transform a collection using a Guava Function. In the following example – we transform a List of names to a List of Integers (length of the name) with Iterables.transform():

@Test
public void whenTransformWithIterables_thenTransformed() {
    Function<String, Integer> function = new Function<String, Integer>() {
        @Override
        public Integer apply(String input) {
            return input.length();
        }
    };

    List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Iterable<Integer> result = Iterables.transform(names, function);

    assertThat(result, contains(4, 4, 4, 3));
}

We can also use the Collections2.transform() API as in the following example:

@Test
public void whenTransformWithCollections2_thenTransformed() {
    Function<String,Integer> func = new Function<String,Integer>(){
        @Override
        public Integer apply(String input) {
            return input.length();
        }
    };

    List<String> names = 
      Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Collection<Integer> result = Collections2.transform(names, func);

    assertEquals(4, result.size());
    assertThat(result, contains(4, 4, 4, 3));

    result.remove(3);
    assertEquals(3, names.size());
}

Note that the output of Collections.transform() is a live view of the original Collection – changes to one affect the other.

And – same as before – if we try to add an element to the output Collection, an UnsupportedOperationException will be thrown.

8. Create Function from Predicate

We can also create Function from a Predicate using Functions.fromPredicate(). This is, of course, going to be a function that transforms the inputs to Boolean, according to the condition of the predicate.

In the following example, we transform a List of names to a list of booleans where each element represents if the name contains “m”:

@Test
public void whenCreatingAFunctionFromAPredicate_thenCorrect() {
    List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Collection<Boolean> result =
      Collections2.transform(names,
      Functions.forPredicate(Predicates.containsPattern("m")));

    assertEquals(4, result.size());
    assertThat(result, contains(false, false, true, true));
}

9. Composition of Two Functions

Next – let’s take a look at how to transform a Collection using a composed Function.

Functions.compose() returns the Composition of Two Functions as it applies the second Function on the output of the first Function.

In the following example – the first Function transform the name into its length, then the second Function transforms the length to a boolean value which represents if the name’s length is even:

@Test
public void whenTransformingUsingComposedFunction_thenTransformed() {
    Function<String,Integer> f1 = new Function<String,Integer>(){
        @Override
        public Integer apply(String input) {
            return input.length();
        }
    };

    Function<Integer,Boolean> f2 = new Function<Integer,Boolean>(){
        @Override
        public Boolean apply(Integer input) {
            return input % 2 == 0;
        }
    };

    List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Collection<Boolean> result = 
      Collections2.transform(names, Functions.compose(f2, f1));

    assertEquals(4, result.size());
    assertThat(result, contains(true, true, true, false));
}

10. Combine Filtering and Transforming

And now – let’s see another cool API that Guava has – one that will actually allow us to chain filtering and transforming together – the FluentIterable.

In the following example – we filter the List of names then transform it using FluentIterable:

@Test
public void whenFilteringAndTransformingCollection_thenCorrect() {
    Predicate<String> predicate = new Predicate<String>() {
        @Override
        public boolean apply(String input) {
            return input.startsWith("A") || input.startsWith("T");
        }
    };

    Function<String, Integer> func = new Function<String,Integer>(){
        @Override
        public Integer apply(String input) {
            return input.length();
        }
    };

    List<String> names = Lists.newArrayList("John", "Jane", "Adam", "Tom");
    Collection<Integer> result = FluentIterable.from(names)
                                               .filter(predicate)
                                               .transform(func)
                                               .toList();

    assertEquals(2, result.size());
    assertThat(result, containsInAnyOrder(4, 3));
}

It is worth mentioning that, in some cases, the imperative version is more readable and should be preferred to the functional approach.

11. Conclusion

Finally, we learned how to filter and transform collections using Guava. We used the Collections2.filter() and Iterables.filter() APIs for filtering, as well as Collections2.transform() and Iterables.transform() to transform collections.

Finally, we took a quick look at the very interesting FluentIterable fluent API to combine both filtering and transforming.

The implementation of all these examples and code snippets can be found in the GitHub project – this is a Maven-based project, so it should be easy to import and run as it is.


« 上一篇: Baeldung周报41
» 下一篇: Spring @Scheduled注解