11 minutes read

As you know from previous topics, the for loop is used in Kotlin for iterating through ranges, arrays, and different types of collections. Also, we learned that collections in Kotlin have a special construction called iterator, which provides methods to iterate through collections and process them. It can be used not only in standard collections but also in your own classes. In this topic, we will see how to do it.

Iterator as interface

Iterator is a useful construction in programming languages like Java and Kotlin, which comes in handy when you want to process a sequence of elements one by one: for example, print each value or even remove some element. In order to be able to use this construction, the class that presents a collection of elements should implement the Iterable interface, which declares the iterator() function. This function creates and returns an Iterator object, which sequentially accesses collection elements.

Iterator has two basic functions – next() and hasNext(). hasNext() is used to check if there are any following elements in iteration. When you call next(), it returns the current element and moves the pointer of the iterator to the next element if it exists in iteration. Here is an example:

val alphabet = listOf('a', 'b', 'c', 'd', 'e')
val alphabetIterator = alphabet.iterator()
while (alphabetIterator.hasNext()) {
    print(alphabetIterator.next() + " ") // a b c d e
}

If you want to be able to modify elements during iteration over a sequence, you need to use MutableIterator – a version of iterator that supports removing elements during iteration. Adding and modifying elements is supported only by MutableListIterator, which works with MutableList.

val colors = mutableListOf("red", "green", "blue", "white") 
val mutableIterator = colors.listIterator()

mutableIterator.next()
mutableIterator.remove()    
println("After : $colors") // After: [green, blue, white]
mutableIterator.add("black")
println("After : $colors") // After: [black, green, blue, white]

Collection as interface

In Kotlin, there are two universal interfaces for working with a sequence of elements – Iterable and MutableIterable. Collection is an interface that represents a generic collection of elements, and it inherits from Iterable. Collection differs from Iterable in providing methods like get(), find(), filter(), count(), etc. As you can see from the scheme below, List and Set are inheritors of Collection:

interface diagram for working with a sequence of elements

As you may remember from previous topics, Iterable does not support changes to collection: for example, you cannot remove an element from a collection that inherits from Collection or add a new element to it. If you want to be able to add or remove elements, the collection you operate on should implement the interface MutableCollection. MutableCollection provides such methods as add(), addAll(), remove(), removeAll(), drop(), etc. This interface is implemented by the mutable analogues of List and SetMutableList and MutableSet respectively.

As you can see, standard collections like List and Set inherit from Iterable, that is why you can create an object of iterator for processing these collections. Let's see next how to make our own implementation of Iterable and create an iterator for it.

Creating your own iterator

Let’s imagine we have a simple class Message, which represents a message with a short text and a pointer to the next message:

class Message(var text: String, var next: Message? = null) { }

We can have a lot of messages and we want to store them, for example, in MessageBox. Also, it would be nice to read messages one by one, so we need to be able to iterate over them.

Let's create the MessageBox class, it will be our custom realization of the sequence of messages. It has its head, which refers to the first message in MessageBox, and tail – it refers to the last one. If we want to iterate through MessageBox, we should inherit it from Iterable<Message> and override its function iterator(). This function returns an object of MessageBoxIterator, which we will create later.

class MessageBox(var head: Message, var tail: Message = head) : Iterable<Message> {

    init {
        if (tail != head) {
            head.next = tail
        }
    }

    fun add(newMessage: Message) {
        tail.next = newMessage // change 'next' pointer of the former last element to a new message
        tail = newMessage // new message becomes a new tail
    }

    override fun iterator(): Iterator<Message> {
        return MessageBoxIterator(this)
    }
}

Also, we've declared a function add() for adding new messages to MessageBox.

As MessageBoxIterator implements Iterator<Message>, we should override hasNext() and next(), which will enable us to get the next element. We've also declared a variable current – the current object of the sequence, to which MessageBoxIterator points.

class MessageBoxIterator(messageBox: MessageBox) : Iterator<Message> {

    private var current: Message = Message("EMPTY_PRE_HEAD", next = messageBox.head)

    override fun hasNext(): Boolean {
        return current.next != null
    }

    override fun next(): Message {
        if (current.next == null) throw NoSuchElementException()

        current = current.next!!
        return current
    }
}

As you can see, here we use Kotlin keywords private and lateinit. The former one is a visibility modifier. This modifier prohibits changing the value of the boolean variable isAccessed outside the class. As you can remember from the topic about Iterator, when an object of the iterator is created, it points to the position before the first element of the collection. isAccessed has a true value if we've already called the next() function and the iterator points to some element; the value is false if we haven't accessed the collection yet. In order to declare non-null types that will be initialized later, we use the keyword lateinit in the variable's declaration. You can read more about it.

Let's try to use what we've done:

fun main() {
    var messageBox = MessageBox(Message("hello!"))
    messageBox.add(Message("I am from hyperskill"))
    messageBox.add(Message("which programming language do you study?"))

    val messageIterator = messageBox.iterator()
    while (messageIterator.hasNext()) {
        println(messageIterator.next().text)
    }

}

Here's the result:

hello!
I am from hyperskill
which programming language do you study?

If we try to invoke the next() function after iterating through all the elements of MessageBox, NoSuchElementException will be thrown. It means that after iterating over the whole sequence, the iterator can't be used anymore and you should create a new Iterator object.

For loop and iterator

Every class that implements the Iterable interface can be used in an enhanced for loop. If we create our custom class, we need to implement the Iterable interface and provide an iterator.

For example, the interface List extends Collection, which in its turn extends Iterable, and that's why we can get access to each element of List inside a for loop. Let's look at the example below:

val languages = listOf("java", "kotlin", "python")
for (lang in languages) {
  println(lang) 
}
/*
 java
 kotlin
 python
*/

But if we try to use the class File, for example, for reading lines from the file inside an enhanced for loop, we will get an error because File doesn't implement the Iterable interface:

var file = File("kotlin.txt")
for(line in File){ // compile error
   ...
}

The for loop can be used with Iterator in the following way:

val letters = listOf("k", "o", "t", "l", "i", "n")
val iterator = letters.iterator()
for (letter in iterator) {
  print(letter) // kotlin
}

Conclusion

In this topic, you have learned about Iterator and Collection as interfaces. We've discovered the difference between Iterable and MutableIterable and discussed how to work with mutable and immutable sequences of elements.

Also, we have implemented Iterable and Iterator by creating our classes and tried working with them. Remember that if you want to create a class that represents a sequence of elements and iterate over it, your class should inherit from Iterable and override its method iterator(). If you need to create your own iterator, you can do it by implementing Iterator.

50 learners liked this piece of theory. 14 didn't like it. What about you?
Report a typo