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 Guava 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)
This is similar to the above method, but it takes a default value in addition to the iterator and the position. It returns the default value if the iterator is empty or position is more than the number of remaining elements. This can still throw IndexOutOfBoundsException if the position is negative.
String elementAtIndexFive = Iterators.get(iterator, 5, "DoesNotExist");
System.out.println(elementAtIndexFive); //DoesNotExist
T getLast(Iterator<T> iterator)
Returns the last element of the iterator. Throws NoSuchElementException if the iterator is empty.
T getLast(Iterator<T> iterator, T defaultValue)
Returns the last element of the iterator. Returns the default value if the iterator is empty
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
T getOnlyElement(Iterator<T> iterator, T defaultValue)
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.
Size:
int size(Iterator<T> iterator)
Returns the number of elements in the iterator.
Contains:
boolean contains(Iterator<T> iterator, Object element)
Returns true if the iterator has the element passed; false otherwise
Frequency:
frequency(Iterator<?> iterator, Object element)
Returns the number of times the specified element occurs in 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.
To learn more about the Google Guava String utility methods, have a look at
References
https://github.com/google/guava/wiki/CollectionUtilitiesExplained