As you may know, you can iterate through a collection or an array in several ways. For example, you can use the for loop for this purpose. Besides, the Kotlin Standard Library provides a special construction called an iterator. You can use both methods, no matter what a collection or an array stores. In this topic, you will learn more about the mechanisms and the use of iterators.
Iterator and immutable collections
An iterator provides access to collection elements sequentially, regardless of their type. It can be seen as a movable pointer to an element of the collection.
In order to use an iterator, you should call the iterator() function. iterator() returns a special object that can iterate over the collection.
When the variable of an iterator is created, it points to the position before the first element of the collection. If you want to move to the next element, call the next() function. It returns the current element and moves the iterator's pointer to the next element. The hasNext() function can help you to find out if there are any elements left in the iteration: it returns "true" if the iteration still has some elements.
Iterators can be called for different kinds of collections: List, Map, and Set.
Let's see an example of its usage with a set:
var set = setOf("cat", "dog", "crocodile", "snake")
var iterator = set.iterator()
while (iterator.hasNext()) {
print(iterator.next() + " ") // cat dog crocodile snake
}
Here we have a set of strings, and we create an iterator for this set. We make a loop with the help of functions hasNext() and next() and print each element of the set. Notice that when the iterator passes through the last element of a collection, it can no longer retrieve elements. If you try to invoke next() from the iterator after passing the last element in a collection (or for an empty collection), NoSuchElementException will be thrown. In this situation, you should create a new iterator for the collection.
Below you can see how the iterator moves over collections.
The start of a collection:
The next step:
The end of a collection:
Instead of using the for loop, iterators support the forEach() function. This function takes an action and performs it on each entry of the collection. You can use lambda expressions and method references inside it.
Let's see an example of using the forEach() function with a map:
var map = mapOf("John" to "chocolate", "Mary" to "sweets", "Sara" to "marmalade")
var iterator = map.iterator()
iterator.forEach { (key, value) ->
println("$key likes $value")
}
/*
John likes chocolate
Mary likes sweets
Sara likes marmalade
*/
forEach() takes the println() function and outputs each entry of the map.
As you can see, with an iterator, we iterate through immutable collections and read values, but we can't modify the collection. In the next paragraph, you will learn how you can change a collection with an iterator.
Iterator and mutable collections
Another special iterator variation is MutableIterator. It differs from the previous one in that it works with mutable collections, i.e., with those that can be changed after their creation. MutableIterator extends Iterator and provides the remove() function, which removes the last collection element returned by the iterator.
An important point in using an iterator (unlike the for...in loop) for mutable collections is its ability to change the mutable collection because the standard for loop in Kotlin doesn't allow us to do it.
val food = mutableSetOf("donuts", "cakes", "tarts")
val mutableIterator = food.iterator()
mutableIterator.next()
mutableIterator.remove()
println("Result : $food")// Result: [cakes, tarts]
As you can see, in order to be able to remove an element from the collection, you need to call next() because we can remove only the element returned by the iterator.
List iterator
Lists have a special type of iterator called ListIterator. It allows you to iterate through the list in both directions: forward and backward, while iterators for Set or Map can iterate only forward. Also, ListIterator is able to get the positions of the next and previous elements.
In addition to the common Iterator methods, ListIterator has the following methods of its own:
fun nextIndex(): Intreturns the index of the element that would be returned by calling thenext()function;fun previous(): Treturns the previous element of the list and moves the iterator's pointer backward;fun hasPrevious(): Booleanreturns "true" if there are elements in the iteration before the current element;fun previousIndex(): Intreturns the index of the element that would be returned by calling theprevious()function.
Let's see an example:
val strings = listOf("i", "like", "donuts")
val listIterator = strings.listIterator()
println("Iterating forwards:")
while (listIterator.hasNext()) listIterator.next()
println("Iterating backwards:")
while (listIterator.hasPrevious()) {
print("index: ${listIterator.previousIndex()}")
println(", value: ${listIterator.previous()}")
}
Here is the result:
Iterating forwards:
Iterating backwards:
index: 2, value: donuts
index: 1, value: like
index: 0, value: i
Firstly, we traversed the list till its end. Then, we iterated through the list again, but now – backwards. As you can see, ListIterator can still be used after it reaches the last element.
Mutable lists have their own version of Iterator called MutableListIterator. MutableListIterator is unique for being able not only to remove elements but also replace them and add new ones while traversing the collection.
val words = mutableListOf("i", "know", "Claire")
val mutableListIterator = words.listIterator()
mutableListIterator.next()
mutableListIterator.next()
mutableListIterator.set("don't know")// i , don't know, Claire
mutableListIterator.add("John")
println(words)// i, don't know, John, Claire
We moved the iterator's pointer to the third element, and the iterator returned the second element. Then we replaced the second element with the function set(). set() replaces the last element returned by next() or previous(). add() adds a new element into the list before the element which would be returned by next().
Conclusion
The Kotlin Standard Library's Iterator provides convenient methods for working with different types of collections. By calling the function iterator(), you can get an object of Iterator for traversing the collection.
Generally, Iterator provides several methods: the forEach method allows performing the given operation on every element of the collection, next() allows moving to the next element, and hasNext() tells us if there are any elements left in the iteration. Iterator is also used for iterating over immutable collections, and its inheritor called MutableIterator allows you to remove elements.
A special iterator implementation ListIterator for List allows you to iterate the list in both directions, and MutableListIterator for mutable lists allows replacing and adding elements during the iteration.