Introduction
Google Guava FluentIterable is a discouraged (but not deprecated) precursor to Java’s superior Stream library. The FluentIterable class in Google Guava has methods to create a FluentIterable, extract elements from it (retrieve elements), query the iterable and to convert the elements in a fluent iterable into a new collection. In this post, we will look into the functionalities provided by the FluentIterable class.
Java Streams vs Google Guava FluentIterable
A Java stream is superior to the Google Guava FluentIterable, but still the FluentIterable is useful. The main difference between a Java stream and a Google Guava FluentIterable is that a Java stream is single-use i.e., once we add any terminal operation to the stream (like forEach, findFirst or collect), it becomes invalid and we cannot use it any longer and we’ll need a new stream instance from the original source. But a FluentIterable is for multiple-use and it implements the Iterable interface of Java.
Stream offers many other features which are not part of the FluentIterable, like min/max, distinct, reduce, sorted and collect. And unlike a stream, we cannot parallelize it. Also, note that a FluentIterable offers other useful methods (we will see in this post) which aren’t available on a Java stream.
Creating a Google Guava FluentIterable
We can create a FluentIterable using three static methods:
of()
from()
concat()
FluentIterable.of()
The static method of() (a generic method) takes n elements and returns a FluentIterable containing those elements in order. We should pass at least one element and the others are optional (var-args).
In the below example, we pass four strings to the FluentIterable.of() method. It returns a FluentIterable containing these four strings.
FluentIterable<String> fluentIterable = FluentIterable.of("a", "b", "c", "d");
System.out.println(fluentIterable); //[a, b, c, d]
To create an empty FluentIterable, there is an overloaded of() method that takes no arguments.
FluentIterable<String> emptyFluentIterable = FluentIterable.of();
System.out.println(emptyFluentIterable); //[]
FluentIterable.from()
There are two overloaded FluentIterable.from() methods. The first takes an array and returns a new FluentIterable, which has all the elements in the array.
String[] arr = new String[]{"a", "b", "c", "d"};
FluentIterable<String> fluentIterable = FluentIterable.from(arr);
System.out.println(fluentIterable); //[a, b, c, d]
The FluentIterable it returns is an unmodifiable view of the array. Any changes in the array would be visible in the FluentIterable.
String[] arr = new String[]{"a", "b", "c", "d"};
FluentIterable<String> fluentIterable = FluentIterable.from(arr);
System.out.println(fluentIterable); //[a, b, c, d]
arr[0] = "e";
System.out.println(fluentIterable); //[e, b, c, d]
The second overloaded from() takes an Iterable and returns a fluent iterable (which wraps the passed Iterable).
List<String> list = List.of("a", "b", "c", "d");
FluentIterable<String> fluentIterable = FluentIterable.from(list);
System.out.println(fluentIterable); //[a, b, c, d]
FluentIterable.concat()
There are five overloaded concat() methods. The first three concat() methods take two, three and four Iterables and return a FluentIterable which combines the passed Iterables.
Example - Constructing a FluentIterable from two Iterators is shown below. The returned fluent iterable has an Iterator that traverses the elements in the first passed iterable, followed by the elements in the second passed iterable.
In the below example, we have two Iterables (Lists) and we pass them to the concat() method. The resulting FluentIterable iterates over the two passed Iterables.
List<String> list1 = new ArrayList<>(List.of("a", "b", "c", "d"));
List<String> list2 = List.of("e", "f");
FluentIterable<String> concatenatedFluentIterable = FluentIterable.concat(list1, list2);
System.out.println(concatenatedFluentIterable); //[a, b, c, d, e, f]
There are two other versions of the concat() method which combine several iterables as shown below.
public static <T> FluentIterable<T> concat(Iterable<? extends T>... inputs)
public static <T> FluentIterable<T> concat(
final Iterable<? extends Iterable<? extends T>> inputs)
The first one takes a varargs of Iterables and returns a fluent iterable over them. It traverses over the elements from the passed Iterable in the order we pass.
The second concat method takes an Iterable of Iterables.
Using the same two lists we’ve used earlier, we have,
FluentIterable<String> concatenatedFluentIterable = FluentIterable
.concat(list1, list2, list1, list2, list1, list2);
Prints,
[a, b, c, d, e, f, a, b, c, d, e, f, a, b, c, d, e, f]
An example of passing an Iterable of Iterable is shown below. We have a nested list, which is our Iterable of Iterable. Note that List<List<String>>
is a subtype of Iterable<? extends Iterable<? extends String>>
.
List<List<String>> lists = List.of(list1, list2);
FluentIterable<String> concatenatedFluentIterable = FluentIterable.concat(lists);
System.out.println(concatenatedFluentIterable); //[a, b, c, d, e, f]
Operations on a FluentIterable
Now we have looked at how to construct a FluentIterable. Let us examine the operations we can perform on a Google Guava FluentIterable.
Append to a FluentIterable - append()
Once we have an instance of FluentIterable, we can use the append() method to add more elements to the FluentIterable.
The first overloaded version of the append() takes a var-args of elements. It internally creates an Iterable of these elements and concatenates with the fluent iterable on which we call the append method.
In the below example, we append two more strings to the fluent iterable instance we have to get back a new fluent iterable.
FluentIterable<String> fluentIterable = FluentIterable.of("a", "b", "c", "d");
FluentIterable<String> appendedFluentIterable = fluentIterable.append("e", "f");
System.out.println(appendedFluentIterable); //[a, b, c, d, e, f]
There is another overloaded version which takes an Iterable as an argument.
FluentIterable<String> fluentIterable = FluentIterable.of("a", "b", "c", "d");
FluentIterable<String> appendedFluentIterable = fluentIterable.append(List.of("e", "f"));
System.out.println(appendedFluentIterable); //[a, b, c, d, e, f]
Check if an object is present in a FluentIterable
We can use the contains() method to check if the fluent iterable contains any object. It internally iterates over the elements of the iterator and checks for equality using the equals method.
FluentIterable<String> fluentIterable = FluentIterable.from(List.of("a", "b", "c"));
System.out.println(fluentIterable.contains("a")); //true
System.out.println(fluentIterable.contains("c")); //true
System.out.println(fluentIterable.contains("d")); //false
Copy elements into a collection - copyInto()
The copyInto() method looks like,
public final <C extends Collection<? super E>> C copyInto(C collection)
It takes an object which is a Collection and copies all elements from the fluent iterable into the passed collection. It returns the same collection instance that was passed.
FluentIterable<String> fluentIterable = FluentIterable.of("a", "b", "c");
List<String> strings = new ArrayList<>();
fluentIterable.copyInto(strings);
System.out.println(strings); //[a, b, c]
If we call the copyInto() method again, it will append the elements in the FluentIterable again to the collection.
fluentIterable.copyInto(strings);
System.out.println(strings); //[a, b, c, a, b, c]
Since it accepts a Collection<? super E>
, the below code works.
List<Object> objects = new ArrayList<>();
fluentIterable.copyInto(objects);
System.out.println(objects); //[a, b, c]
Joining the elements
When we call the join() method, it joins all the elements of the fluent iterable using the passed Joiner.
The below code shows how to join the elements of a fluent iterable using ,
as the joiner. To learn about more kinds of Joiners, refer to Joiner in Google Guava post.
FluentIterable<String> fluentIterable = FluentIterable.of("a", "b", "c");
System.out.println(fluentIterable.join(Joiner.on(", ")));
Cycling over the elements of a FluentIterable
This is a very useful method to convert a fluent iterable which indefinitely cycles over its elements.
List<String> list = List.of("a", "b", "c");
FluentIterable<String> fluentIterable = FluentIterable.from(list);
FluentIterable<String> cycledFluentIterable = fluentIterable.cycle();
System.out.println(cycledFluentIterable); //[a, b, c] (cycled)
As the resulting iterator is infinite, we have to be cautious in using the returned FluentIterable.
cycledFluentIterable.stream()
.limit(8)
.forEach(System.out::println);
Prints,
a
b
c
a
b
c
a
b
Other miscellaneous simple methods of a FluentIterable
FluentIterable - get, isEmpty, and size methods
The get() takes an index or position and returns the element at the specified position in the iterable.
FluentIterable<String> fluentIterable = FluentIterable.of("a", "b", "c");
System.out.println(fluentIterable.get(0)); //a
System.out.println(fluentIterable.get(1)); //b
System.out.println(fluentIterable.get(2)); //c
It will throw an IndexOutOfBoundsException if the passed position is invalid.
//java.lang.IndexOutOfBoundsException: index (3) must be less than size (3)
System.out.println(fluentIterable.get(3));
The isEmpty() checks if the fluent iterable is empty and the size() returns the number of elements in this fluent iterable.
FluentIterable<String> fluentIterable = FluentIterable.of("a", "b", "c");
System.out.println(fluentIterable.isEmpty()); //false
fluentIterable = FluentIterable.of();
System.out.println(fluentIterable.isEmpty()); //true
FluentIterable<String> fluentIterable = FluentIterable.of("a", "b", "c");
System.out.println(fluentIterable.size()); //3
fluentIterable = FluentIterable.of();
System.out.println(fluentIterable.size()); //0
FluentIterable - first and last methods
The first() method returns an Optional having the first element of the fluent iterable. Similarly, the last() method returns an Optional having the last element of the fluent iterable. If the iterable is empty, it returns an empty Optional when calling the first() or last().
Note that it returns a Google Guava Optional and not a Java Optional.
FluentIterable<String> fluentIterable = FluentIterable.of("a", "b", "c");
System.out.println(fluentIterable.first()); //Optional.of(a)
System.out.println(fluentIterable.last()); //Optional.of(c)
System.out.println(FluentIterable.of().first()); //Optional.absent()
System.out.println(FluentIterable.of().last()); //Optional.absent()
FluentIterable - Java Stream equivalent methods
FluentIterable#transform
The transform() method on a Google Guava FluentIterable is like a map on a Java Stream. It takes a Function (Google Guava Function) and applies each element of the Iterable to this function, returning a new FluentIterable with the results of applying the function.
In the below example, we have a FluentIterable over some strings. We apply the transform() method on the Guava FluentIterable to map (transform) each element into an Integer, thus returning a FluentIterable<Integer>
which has the length of each input element in the original FluentIterable.
FluentIterable<String> fluentIterable = FluentIterable
.of("Java", "C", "C++", "C#", "Go");
FluentIterable<Integer> resultFluentIterable = fluentIterable.transform(String::length);
System.out.println(resultFluentIterable); //[4, 1, 3, 2, 2]
FluentIterable#transformAndConcat
The transformAndConcat is like a flatMap on a Java Stream. We pass a function to map each element of an Iterable and the transformAndConcat applies the function to each element and returns a FluentIterable with all the results.
With the method signature shown below, we can see that the passed function maps an element of type E
to an Iterable<T>
. The transformAndConcat method thus returns a FluentIterable<T>
by combining all the Iterables resulting from each mapping.
FluentIterable<T> transformAndConcat(
Function<? super E, ? extends Iterable<? extends T>> function)
Let us create a class called StringIterable
, which is an Iterable<Character>
.
public class StringIterable implements Iterable<Character> {
private final String string;
private StringIterable(String string) {
this.string = string;
}
@Override
public Iterator<Character> iterator() {
return new StringIterator(string);
}
}
The StringIterator
class is shown below. It has a string, and it iterates over the individual characters of that string.
public class StringIterator implements Iterator<Character> {
private final String string;
private int index;
private StringIterator(String string) {
this.string = string;
this.index = 0;
}
@Override
public boolean hasNext() {
return index < string.length();
}
@Override
public Character next() {
return string.charAt(index++);
}
}
To the transformAndConcat
, we pass the method reference StringIterable::new
which takes a String and returns an Iterable<Character>
.
FluentIterable<String> fluentIterable = FluentIterable.of("Java", "C", "C++");
FluentIterable<Character> resultFluentIterable = fluentIterable
.transformAndConcat(StringIterable::new);
System.out.println(resultFluentIterable); //[J, a, v, a, C, C, +, +]
FluentIterable#forEach
The forEach method accepts a Java Consumer and internally it iterates over the elements and passes them to the passed Consumer.
FluentIterable<String> fluentIterable = FluentIterable
.of("Java", "C", "C++", "C#", "Go");
Prints,
Java
C
C++
C#
Go
FluentIterable - filter() and skip()
The filter() and skip() are similar to that of the Java Stream’s filter and skip operation. The filter takes a Google Guava Predicate and returns a new FluentIterable, which has only the elements which pass the passed predicate.
The skip() takes the number of elements to skip (numberToSkip) and returns a view of this fluent iterable that skips the first numberToSkip elements. If it has lesser than numberToSkip elements, then it returns an empty iterable.
The below code filters the elements whose length is more than 2.
FluentIterable<String> fluentIterable = FluentIterable
.of("Java", "C", "C++", "C#", "Go");
fluentIterable
.filter(s -> s.length() > 2)
.forEach(System.out::println);
Prints,
Java
C++
Using skip() on the above FluentIterable, we get,
fluentIterable
.skip(2)
.forEach(System.out::println);
Prints,
C++
C#
Go
FluentIterable - firstMatch(), allMatch() and anyMatch()
All of these take a Google Guava Predicate as an argument.
- firstMatch evaluates the condition on the elements of the fluent iterable (in order) and returns the first element which satisfies the passed condition as an (Google Guava) Optional.
- allMatch returns true if all the elements satisfy the passed predicate.
- anyMatch returns true if at least one element in this fluent iterable satisfies the predicate.
FluentIterable<String> fluentIterable = FluentIterable
.of("TS", "C#", "Go");
//Optional.of(TS)
System.out.println(fluentIterable.firstMatch(s -> s.length() == 2));
System.out.println(fluentIterable.allMatch(s -> s.length() == 2)); //true
System.out.println(fluentIterable.allMatch(s -> s.length() > 2)); //false
System.out.println(fluentIterable.anyMatch(s -> s.contains("#"))); //true
}
FluentIterable#stream
To convert a Google Guava FluentIterable to a Java Stream, we can use the stream() method.
Stream<String> stream = fluentIterable.stream();
Methods in FluentIterable which return a Collection
FluentIterable#index
The index() method takes a Guava Function to map each element (of type E) of this fluent iterable to an element of type K (key). It then returns an ImmutableListMultimap, which is keyed by the mapped value and the value being the original element of the FluentIterable.
public final <K> ImmutableListMultimap<K, E> index(
Function<? super E, K> keyFunction)
Since it is a Multimap, we can map more than one element of the iterable to the same key. In the returned Multimap, keys appear in the order they were first encountered, and the multiple values (if any) mapped to each key appear in the same order as they were encountered.
FluentIterable<String> fluentIterable = FluentIterable
.of("Java", "C", "C++", "C#", "Go");
ImmutableListMultimap<Integer, String> multimap = fluentIterable.index(String::length);
System.out.println(multimap);
System.out.println(multimap.size());
Here, we have a FluentIterable having programming language names. We call the index() method with a function which converts a string to its length as the keyFunction. The returned multiMap is shown below and its size is 5 (since there are 5 values).
{4=[Java], 1=[C], 3=[C++], 2=[C#, Go]}
5
The below code demonstrates the order in which the resultant multimap is built.
FluentIterable<String> fluentIterable = FluentIterable
.of("Java", "TS", "C", "C++", "C#", "Go");
multimap = fluentIterable.index(String::length);
System.out.println(multimap);
System.out.println(multimap.size());
Running this, we get,
{4=[Java], 2=[TS, C#, Go], 1=[C], 3=[C++]}
6
When comparing to Java Streams, this is sort of equivalent to use Collectors.groupingBy, but the ordering will be different.
Map<Integer, List<String>> result = Stream.of("Java", "TS", "C", "C++", "C#", "Go")
.collect(Collectors.groupingBy(String::length));
System.out.println(result);
System.out.println(result.size());
Prints,
{1=[C], 2=[TS, C#, Go], 3=[C++], 4=[Java]}
4
FluentIterable#uniqueIndex
The uniqueIndex takes a keyFunction which is the same as what index() function did. But it returns an ImmutableMap which is indexed by the values derived from the passed function and the original element as the value. The entries appear in the same order as they appeared in this fluent iterable.
public final <K> ImmutableMap<K, E> uniqueIndex(
Function<? super E, K> keyFunction)
An example is shown below.
FluentIterable<String> fluentIterable = FluentIterable
.of("Java", "C", "C++", "C#");
ImmutableMap<Integer, String> immutableMap = fluentIterable.uniqueIndex(String::length);
System.out.println(immutableMap); //{4=Java, 1=C, 3=C++, 2=C#}
A Stream equivalent would be to Collectors.toMap, but the ordering will be different.
Map<Integer, String> resultMap = Stream.of("Java", "C", "C++", "C#")
.collect(Collectors.toMap(String::length, Function.identity()));
System.out.println(resultMap); //{1=C, 2=C#, 3=C++, 4=Java}
Unlike index(), we cannot have more than one element in the iterable map to the same key. We would get an IllegalArgumentException if it happens. An example follows:
//throws
//java.lang.IllegalArgumentException: Multiple entries with same key: 2=C# and 2=TS.
// To index multiple values under a key, use Multimaps.index.
FluentIterable<String> fluentIterable = FluentIterable
.of("Java", "TS", "C", "C++", "C#", "Go");
fluentIterable.uniqueIndex(String::length);
FluentIterable#toArray
We pass a class type (Class<E>
) to the toArray() method and it returns the elements of the fluent iterable as an array (in iteration order).
FluentIterable<String> fluentIterable = FluentIterable
.of("Java", "C", "C++", "C#", "Go");
String[] result = fluentIterable.toArray(String.class);
System.out.println(Arrays.toString(result)); //[Java, C, C++, C#, Go]
FluentIterable - toList() and toSortedList()
FluentIterable#toList returns an ImmutableList having all the elements from this fluent iterable in iteration order.
FluentIterable<String> fluentIterable = FluentIterable
.of("Java", "C", "C++", "C#", "Go", "Go");
ImmutableList<String> immutableList = fluentIterable.toList();
System.out.println(immutableList); //[Java, C, C++, C#, Go, Go]
The toSortedList() takes a Comparator and returns an ImmutableList having all the elements of the fluent iterable sorted by the comparator.
We can sort a fluent iterable into a list as,
FluentIterable<String> fluentIterable = FluentIterable
.of("Java", "C", "C++", "C#", "Go", "Go");
ImmutableList<String> immutableList = fluentIterable.toSortedList(Comparator.naturalOrder());
System.out.println(immutableList); //[C, C#, C++, Go, Go, Java]
FluentIterable - toSet(), toSortedSet(), and toMultiset()
The toSet() returns the elements of the fluent iterable as an ImmutableSet. Thus, it will remove duplicates.
FluentIterable<String> fluentIterable = FluentIterable
.of("Java", "C", "C++", "C#", "Go", "Go", "Java");
ImmutableSet<String> immutableSet = fluentIterable.toSet();
System.out.println(immutableSet); //[Java, C, C++, C#, Go]
System.out.println(immutableSet.size()); //5
The toSortedSet() takes a comparator and returns an ImmutableSortedSet which has the fluent iterable’s elements sorted as per the ordering enforced by the passed comparator.
In the above created fluent iterable, let us sort them by natural order as shown below.
ImmutableSortedSet<String> sortedSet = fluentIterable.toSortedSet(Comparator.naturalOrder());
System.out.println(sortedSet); //[C, C#, C++, Go, Java]
System.out.println(sortedSet.size()); //5
Finally, the toMultiset() method returns an ImmutableMultiset which has all the elements from this fluent iterable. Since it is a multiset, the duplicates will be preserved. As shown in the below example, we have two instances of the string ‘Java’ and the string ‘Go’.
ImmutableMultiset<String> multiset = fluentIterable.toMultiset();
System.out.println(multiset); //[Java x 2, C, C++, C#, Go x 2]
System.out.println(multiset.size()); //7
FluentIterable#toMap
The toMap takes a value mapping function to map each element to a value and returns an ImmutableMap whose keys are the elements in the fluent iterable and value is the result of applying the valueFunction. The map’s iteration order is the iteration order of the iterable.
public final <V> ImmutableMap<E, V> toMap(Function<? super E, V> valueFunction)
Here, we map each element to its length.
FluentIterable<String> fluentIterable = FluentIterable
.of("Java", "C", "C++", "C#", "Go");
ImmutableMap<String, Integer> immutableMap = fluentIterable.toMap(String::length);
System.out.println(immutableMap); //{Java=4, C=1, C++=3, C#=2, Go=2}
If the fluent iterable has duplicate elements, then the behaviour is unspecified. In other words, we cannot guarantee if the valueFunction will be applied to all the duplicate elements and, if it is, we cannot determine which result will be mapped to that key in the returned map.
FluentIterable<String> fluentIterable = FluentIterable
.of("Java", "C", "C++", "C#", "Go", "Go");
ImmutableMap<String, Integer> immutableMap = fluentIterable.toMap(String::length);
System.out.println(immutableMap); //{Java=4, C=1, C++=3, C#=2, Go=2}
Conclusion
This post covered the FluentIterable class in the Google Guava. We learned how to create a FluentIterable and saw the useful methods it offers for working with a FluentIterable. Then, we looked at the Java stream-like methods in a Google Guava FluentIterable and finally saw methods that return a collection.