1. Overview
In this short tutorial, we’re going to see how we can use object expressions to create anonymous inner classes in Kotlin.
First, we’ll get familiar with the Kotlin API for anonymous inner classes. Then we’ll dig deeper to see how things are represented at the bytecode level.
2. Anonymous Inner Classes
In Java, it’s possible to create anonymous inner classes using the “**new ClassName() { … }” syntax. For instance, here we’re creating an anonymous inner class for the NIO‘s Channel interface:
Channel channel = new Channel() {
@Override
public boolean isOpen() {
return false;
}
@Override
public void close() throws IOException {
// omitted
}
};
To create an anonymous inner class in Kotlin, we have to use object expressions. For instance, here’s how we can achieve the same thing in Kotlin:
val channel = object : Channel {
override fun isOpen() = false
override fun close() {
// omitted
}
}
Instead of the new keyword, we’re using the “object :” to represent the object expression syntax. If the supertype has a constructor, we should pass the necessary parameters to that constructor:
val maxEntries = 10000
val lruCache = object : LinkedHashMap<String, Int>(50, 0.75f) {
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<String, Int>?): Boolean {
return size > maxEntries
}
}
Here, we’re creating an anonymous LRU cache using the LinkedHashMap with a custom initial capacity and load factor. When we don’t want to pass any parameters to the constructor, we still have to put the empty parentheses, though:
val map = object : LinkedHashMap<String, Int>() {
// omitted
}
Moreover, we can extend from one class plus multiple interfaces or just multiple interfaces:
val serializableChannel = object : Channel, Serializable {
// omitted
}
Quite interestingly, we can create anonymous inner classes without even extending another class or interface:
val obj = object {
val question = "answer"
val answer = 42
}
println("The ${obj.question} is ${obj.answer}")
Now that we know how to create anonymous inner classes, let’s see what the bytecode looks like.
3. Bytecode Level
To take a peek at the generated bytecode, we should first compile the Kotlin code using kotlinc. If the Kotlin file name is Anonymous.kt, then the following command compiles the Kotlin code into a class file:
$ kotlinc Anonymous.kt
*Then we can see the bytecode via *“**javap -p -c -v”**. For instance, here’s how the channel variable is represented at the bytecode level:
$ javap -c -p -v AnonymousKt
0: new #11 // class AnonymousKt$main$channel$1
3: dup
4: invokespecial #14 // Method AnonymousKt$main$channel$1."<init>":()V
// omitted
InnerClasses:
public static final #11; // class AnonymousKt$main$channel$1
As shown above, the Kotlin compiler first generates a static inner class for the “new Channel { … }” expression. Then, it calls the constructor (the
Here’s what the bytecode looks like when we’re passing some arguments to the constructor:
16: bipush 10 // capacity
18: ldc #17 // float 0.75f (load factor)
20: invokespecial #20 // Method AnonymousKt$main$lruCache$1."<init>":(IIF)V
The constructor for the generated static inner class takes two integers and one float as input — the IIF part. One of those integers is actually helping us to capture the value of the maxEntries variable, and the other two are the constructor parameters:
val maxEntries = 10
val lruCache = object : LinkedHashMap<String, Int>(10, 0.75f) {
// omitted
}
This way, an anonymous inner class can access enclosing variables. This is how closure works for anonymous inner classes.
The key takeaway is, the Kotlin compiler translates the anonymous inner classes to static inner classes in the bytecode.
4. Conclusion
In this quick tutorial, we saw how object expressions can be used to declare anonymous inner classes. We also got familiar with the internal representation of the object expressions.
As usual, all the examples are available over on GitHub.