Google Guava Collections2

Google Guava Collections2 utility class

The Collections2 is a utility class in the Google Guava library. It provides static methods for working with Collection instances. It has four methods viz., filter, transform, permutations and orderedPermutations. We will explore these methods in this post.

Filtering a collection by a predicate

The filter method takes a Collection and a Predicate as arguments and returns a Collection which has the elements which satisfy the predicate. The collection it returns is a live view of the passed collection and hence changes affect one another.

Let us look at an example. We have a list of fruit names as List<String>. We pass this list (collection) and a predicate which returns true if the passed string is of even length. The result is that we get back a collection (a view) which has only the fruits whose length is even.

List<String> fruits = new ArrayList<>(List.of("apple", "orange", "grapes"));
Collection<String> evenLengthFruits = Collections2.filter(fruits, s -> s.length() %2 == 0);
System.out.println(evenLengthFruits); //[orange, grapes]

In this example, ‘apple’ is not part of the result as its length is odd (5 characters).

Since the returned collection is a live view, changes made to either the original collection or the returned collection affect each other as shown below.

If we add an even-length fruit to the original list, it is visible via the returned filtered collection.

fruits.add("banana");
System.out.println(evenLengthFruits); //[orange, grapes, banana]

Similarly, we can add an even-length fruit via the returned view and it will be added to the original list.

evenLengthFruits.add("kiwi");
System.out.println(fruits); //[apple, orange, grapes, banana, kiwi]
System.out.println(evenLengthFruits); //[orange, grapes, banana, kiwi]

Adding and removing via the returned filtered collection view

If we add an element which doesn’t satisfy the predicate view through the returned filtered collection view, it will throw an IllegalArgumentException.

//throws IllegalArgumentException
evenLengthFruits.add("lemon");

If we call methods like remove, removeAll or clear on the filtered collection, it will remove only the elements which satisfy the predicate (filter) from the underlying collection.

In the below example, if we call remove by passing ‘apple’, it won’t be removed as it doesn’t pass the predicate condition. On the other hand, we can remove ‘kiwi’.

evenLengthFruits.remove("apple");
evenLengthFruits.remove("kiwi");
System.out.println(fruits); //[apple, orange, grapes, banana]
System.out.println(evenLengthFruits); //[orange, grapes, banana]

Stream equivalent of Collections2#filter

Rather than using this method, we can also create a stream out of the collection and use Stream#filter to achieve the same result. The difference is the returned (collected) list is not a live-view over the original source collection.

System.out.println(fruits.stream()
            .filter(s -> s.length() % 2 == 0)
            .toList());

Transforming the elements of a collection

The transform method takes a Collection and a Function as arguments and returns a collection which applies the passed function to each element of the collection. Similar to the filter method, the returned collection is a live-view over the underlying collection.

In this example, we pass the fruits list and a function which appends the length of a string to the string delimited by ‘#’.

List<String> fruits = List.of("apple", "orange", "grapes");
Collection<String> transformed = Collections2.transform(fruits, s -> s + "#" + s.length());
System.out.println(transformed); //[apple#5, orange#6, grapes#6]

The returned collection doesn’t support add() or addAll() method. It throws an UnsupportedOperationException. But it supports other methods like size().

//throws UnsupportedOperationException
transformed.add("kiwi");
System.out.println(transformed.size()); //3

Stream equivalent of Collections2#transform

The Java Stream equivalent of this is to use the Stream#map method.

System.out.println(fruits.stream()
                .map(s -> s + "#" + s.length())
                .toList()); //[apple#5, orange#6, grapes#6]

All permutations of the elements of a collection

The permutations method takes a Collection and returns all the permutations of the passed collection.

Note: This is an implementation of the Plain Changes algorithm described in Knuth’s “The Art of Computer Programming”, Volume 4, Chapter 7, Section 7.2.1.2.

In the below example, we pass a list with two elements. The default toString() of the permutation does not print the actual permutation. It only computes the permutation when we iterate over the result.

Collection<List<String>> permutation = Collections2.permutations(List.of("a", "b"));
System.out.println(permutation); //permutations([a, b])

permutation.forEach(System.out::println);

Prints,

[a, b]
[b, a]

An example with three elements is shown below.

Collection<List<String>> permutation = Collections2.permutations(List.of("a", "b", "c"));
System.out.println(permutation);
permutation.forEach(System.out::println);

Prints,

permutations([a, b, c])
[a, b, c]
[a, c, b]
[c, a, b]
[c, b, a]
[b, c, a]
[b, a, c]

If the passed input has equal elements, then some of the generated permutations will be equal.

Collection<List<String>> permutation = Collections2.permutations(List.of("a", "a"));
System.out.println(permutation);
permutation.forEach(System.out::println);

This results in,

permutations([a, a])
[a, a]
[a, a]

An empty collection has only one permutation, which is an empty list.

Collection<List<String>> permutation = Collections2.permutations(List.of());
System.out.println(permutation); //permutations([])
permutation.forEach(System.out::println); //[]

Ordered Permutations of a collection

The orderedPermutations takes an Iterable and returns a Collection containing all the permutations of the passed Iterable in lexicographical order. This is an implementation of the algorithm for Lexicographical Permutations Generation, described in Knuth’s “The Art of Computer Programming”, Volume 4, Chapter 7, Section 7.2.1.2.

Collection<List<String>> orderedPermutations = Collections2.orderedPermutations(List.of("b", "a"));
System.out.println(orderedPermutations);
orderedPermutations.forEach(System.out::println);

Prints,

orderedPermutationCollection([a, b])
[a, b]
[b, a]

As we can see from the result, the permutations are in lexicographical order. An example with three element follows.

Collection<List<String>> orderedPermutations = Collections2.orderedPermutations(List.of("c", "b", "a"));
System.out.println(orderedPermutations);
orderedPermutations.forEach(System.out::println);

Prints,

orderedPermutationCollection([a, b, c])
[a, b, c]
[a, c, b]
[b, a, c]
[b, c, a]
[c, a, b]
[c, b, a]

If the input has two equal elements, then they will be considered as equal. In the below example, we have the list [a, a], but there is only one permutation in the result and not two.

Collection<List<String>> orderedPermutations = Collections2.orderedPermutations(List.of("a", "a"));
System.out.println(orderedPermutations);
orderedPermutations.forEach(System.out::println);
orderedPermutationCollection([a, a])
[a, a]

An empty Iterable has only one permutation, which is an empty list.

Collection<List<String>> orderedPermutations = Collections2.orderedPermutations(List.<String>of());
System.out.println(orderedPermutations); //orderedPermutationCollection([])
orderedPermutations.forEach(System.out::println); //[]

Ordered Permutations of a collection with a custom comparator

There is an overloaded orderedPermutations method which takes an additional Comparator argument for establishing the lexicographical ordering.

Using the same example as before, we pass Comparator.reverseOrder() as the Comparator. Hence, the permutations will be sorted in reverse order.

Note: The Comparator comparing post dives deep into the other methods in the Comparator class.

Collection<List<String>> orderedPermutations = Collections2.orderedPermutations(List.of("a", "b"),
        Comparator.reverseOrder());
System.out.println(orderedPermutations);
orderedPermutations.forEach(System.out::println);

This results in,

orderedPermutationCollection([b, a])
[b, a]
[a, b]

Similarly for the three-element list, we get the same result as before but in the reverse order.

Collection<List<String>> orderedPermutations = Collections2.orderedPermutations(List.of("a", "b", "c"), Comparator.reverseOrder());
System.out.println(orderedPermutations);
orderedPermutations.forEach(System.out::println);

Prints,

orderedPermutationCollection([c, b, a])
[c, b, a]
[c, a, b]
[b, c, a]
[b, a, c]
[a, c, b]
[a, b, c]

As seen before, it will consider duplicate elements as equal and an empty iterable has only one permutation.

Collection<List<String>> orderedPermutations = Collections2.orderedPermutations(List.of("a", "a"), Comparator.reverseOrder());
System.out.println(orderedPermutations); //orderedPermutationCollection([a, a])
orderedPermutations.forEach(System.out::println); //[a, a]

Collection<List<String>> orderedPermutations = Collections2.orderedPermutations(List.<String>of(), Comparator.reverseOrder());
System.out.println(orderedPermutations); //orderedPermutationCollection([])
orderedPermutations.forEach(System.out::println); //[]

Conclusion

In this post, we looked at the four methods from the Google Guava Collections2 utility class. You can check out the other google-guava utils here.

Leave a Reply