Introduction
The Google Guava project contains Google’s core libraries. They provide support for things like collections, caching, string manipulation, and I/O, etc. We will look at one of the classes that provide support for dealing with iterators – the Iterators class. The Google Guava Iterators class contains static utility methods that operate on or return objects of type Iterator.
You can include Google Guava into your project or application from Maven central. Refer to the Getting Google Guava section from one of my previous posts to know how to depend on Google Guava library.
Iterators – A recap
An Iterator provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation. You will normally create an iterator out of a list and access or traverse the element as follows.
List<String> list1 = Arrays.asList("string1", "string2", "string3"); //Using while loop Iterator<String> iterator = list1.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } //Using enhanced for loop for (String element : list1) { System.out.println(element); }
When using an enhanced-for loop, it uses an Iterator behind the scenes. I have a whole blog post on the Iterator Design Pattern – check it out to know more.
Coming back to the Google Guava Iterators, it offers a lot of static utility methods to operate on Iterators. We will discuss a few key ones here.
Google Guava Iterators
All the methods in the Iterators class discussed here are lazy. In other words, it iterates through the elements only when necessary.
An Iterator should not be reused. Once the elements are traversed, we cannot go back (unless we are using a ListIterator). Consider the below code snippet. The contains method returns whether the iterator contains the passed element.
List<String> list1 = Arrays.asList("string1", "string2", "string3"); Iterators.contains(iterator, "string10")); //false Iterators.contains(iterator, "string1")); //false
The first call returns false as the iterator does not have the string string10. But, even the second call returns false though the iterator has string1. This is because the Iterator is empty when we make the second call. The right way to do this is to obtain a fresh iterator instance from the underlying collection.
Iterators.contains(list1.iterator(), "string1"));// true
NOTE: Hence, in this post, wherever iterator is used, it indicates that a fresh copy must be created (list1.iterator()).
The Get methods
T Iterators.get(Iterator<T> iterator, int position)
Returns the element at the specified position. It can throw IndexOutOfBoundsException if the position is negative or greater than or equal to the number of elements remaining in the passed iterator.
String elementAtIndexOne = Iterators.get(iterator, 1); System.out.println(elementAtIndexOne); //string2
T Iterators.get(Iterator<T> iterator, int position, T defaultValue)
String elementAtIndexFive = Iterators.get(iterator, 5, "DoesNotExist"); System.out.println(elementAtIndexFive); //DoesNotExist
String lastElement = Iterators.getLast(iterator); System.out.println(lastElement); //string3 //Call again with the same iterator System.out.println(Iterators.getLast(iterator, "DoesNotExist")); //DoesNotExist
T getOnlyElement(Iterator<T> iterator)
Gets the only element backed up by the iterator. Throws IllegalArgumentException if the iterator has more than one element. It throws NoSuchElementException if the iterator is empty. In such case, use the below one that takes a default value
List<String> oneElementList = Collections.singletonList("string"); iterator = oneElementList.iterator(); System.out.println(Iterators.getOnlyElement(iterator)); //string System.out.println(Iterators.getOnlyElement(iterator, "DoesNotExist")); //DoesNotExist
The Query methods
These class of methods allows us to query the status of the iterator.
System.out.println(Iterators.size(iterator));//3 System.out.println(Iterators.contains(iterator, "string2"));// true System.out.println(Iterators.contains(iterator, "string10"));// false System.out.println(Iterators.frequency(iterator, "string1")); //1
Note: As explained, you have to obtain a fresh copy of iterator each time
Cycle
This is an interesting method where it creates an iterator that cycles indefinitely over the passed elements. Just looping without breaking or removing the elements will result in an infinite loop.
Iterator<String> cycledIterator = Iterators.cycle("string1", "string2", "string3"); int max = 10; int c = 0; while (cycledIterator.hasNext()) { System.out.println(cycledIterator.next()); c++; if (c == max) { break; } }
Outputs:
string1 string2 string3 string1 string2 string3 string1 string2 string3 string1
Concatenation
The concat method concatenates two or more iterators returning a single iterator. The returned iterator iterates over the elements of the passed iterators in order.
List<String> list2 = Arrays.asList("string4", "string5"); Iterator<String> concatenatedIterator = Iterators.concat(list1.iterator(), list2.iterator()); System.out.println(Iterators.toString(concatenatedIterator));
Outputs:
[string1, string2, string3, string4, string5]
We also made use of a friendly method toString to convert an Iterator to a String.
Partition
This divides the passed iterator into an Iterator of Lists where it splits the elements from the iterator into sublists of the specified size. The last list may be of size less than the specified partition size.
List<String> list3 = Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i"); Iterator<List<String>> itr = Iterators.partition(list3.iterator(), 2); while (itr.hasNext()) { System.out.println(itr.next()); }
The Find methods
The find methods take an Iterator<T> and a Predicate<T> and returns the first element that satisfies the predicate. It throws a NoSuchElementException when none of the elements satisfy the predicate. In such case,, two options are possible. Following the pattern seen earlier, use the overloaded version that takes a default value. Or, use the tryFind method. It returns an Optional<T> in that it returns Optional.absent() when no element satisfies the predicate.Note: This is not the Optional from java.util. It is the Optional from Google Guava.
System.out.println(Iterators.find(iterator, "string2"::equals)); //string2 System.out.println(Iterators.find(iterator, "string100"::equals, "N/A")); //N/A System.out.println(Iterators.tryFind(iterator, "string100"::equals)); //Optional.absent()
string2::equals is a method reference describing the lambda
Predicate<String> equalsPredicate = s -> s.equals("string2");
To learn more about Method references, refer to the Method References post.
Conclusion
In this post, we looked at the static utility methods of the Iterators class from Google Guava. We learnt what APIs or methods it offers that can help us in dealing with the Iterators.
References
https://github.com/google/guava/wiki/CollectionUtilitiesExplained