Home > Net >  How and why is Kotlin's `Map` iterable even though it is not `Iterable`?
How and why is Kotlin's `Map` iterable even though it is not `Iterable`?

Time:06-06

As it was in Java, any object that is Iterable can be used in an enhanced for loop. I thought the same held true for Kotlin as well, until I discovered that kotlin.collections.Map is not Iterable at all.

from the standard library source code:

public interface Map<K, out V> {
    // ....

If not, then how is something like this possible?:

val map: Map<String, Int> = HashMap()
// ...
for ((s, i) in map) {
    println(s)
    println(i)
    // works perfectly
}

Finally, is it possibly to make custom classes that support this syntax? If so, how?

CodePudding user response:

The Kotlin for loop is defined by convention:

Some syntax forms in Kotlin are defined by convention, meaning that their semantics are defined through syntactic expansion of one syntax form into another syntax form.

In the case of for(VarDecl in C) Body, the syntax form that it expands to is:

when(val $iterator = C.iterator()) {
    else -> while ($iterator.hasNext()) {
                val VarDecl = $iterator.next()
                <... all the statements from Body>
            }

as specified here in the spec.

Rather than requiring a particular interface, it is only required that overload resolution finds appropriate operator overloads for iterator(), hasNext() and next(). As an extreme example, this compiles!

fun main() {
    for (s in Foo()) { // no error here!
        
    }
}

class Foo {
    operator fun iterator(): Bar = Bar()
}

class Bar {
    operator fun hasNext() = false
    operator fun next() = ""
}

In your case, there exists an extension function iterator for Map:

operator fun <K, V> Map<out K, V>.iterator(): Iterator<Entry<K, V>>

and MutableMap:

@JvmName("mutableIterator") 
operator fun <K, V> MutableMap<K, V>.iterator(): MutableIterator<MutableEntry<K, V>>

The iterator method don't have to be declared in Map - as long as overload resolution can resolve it, the for loop works. The same applies to next and hasNext.

So to make your own class work with for loops, the minimum you need is to declare:

operator fun iterator(): Iterator<Something>

But I would still suggest that you implement Iterable, because then you get all the Iterable extension functions for free :)

  • Related