1. Overview

In Java, we have anonymous classes, which allow us to declare and instantiate anonymous classes in a single expression at the point of use.

In this tutorial, we’ll explore the anonymous classes and objects in Kotlin.

2. Introduction to Anonymous Objects

Instances of anonymous classes are called anonymous objects, as an expression – instead of a name – defines them.

Usually, the anonymous objects are pretty lightweight and for one-time use. In Kotlin, anonymous objects are created by object expressions.

We should note that the object expression we’re talking about is not the object declaration to create a single static instance of a type.

In this tutorial, we’ll first look at how to create anonymous objects from a supertype, such as an abstract class. Then, we’ll address an interesting usage: creating anonymous objects from scratch.

Further, we’ll compare Kotlin’s anonymous objects and their counterpart in Java.

For simplicity, we’ll use unit tests’ assertions to verify whether our anonymous objects are working as expected.

Next, let’s see them in action.

3. Creating Anonymous Objects From an Abstract Class

As in Java, we cannot instantiate an abstract class directly in Kotlin, either. For example, let’s say we have an abstract class called Doc:

abstract class Doc(
    val title: String,
    val author: String,
    var words: Long = 0L
) {
    abstract fun summary(): String
}

As we can see in the class above, Doc has a constructor. But if we try to instantiate it directly:

val article = Doc(title = "A nice article", author = "Kai", words = 420) // won't compile!

The compiler will complain: “cannot create an instance of an abstract class“.

However, we can use the object expression to create an object of an anonymous class that inherits Doc:

val article = object : Doc(title = "A nice article", author = "Kai", words = 420) {
    override fun summary() = "Title: <$title> ($words words) By $author"
}

As the code above shows, the object expression syntax for creating an anonymous object from a type is:

object : TheType(...constructor parameters...) { ... implementations ... }

In our example, we’ve implemented the abstract function summary. Now, let’s verify if the article object is what we’re expecting:

article.let {
    assertThat(it).isInstanceOf(Doc::class.java)
    assertThat(it.summary()).isEqualTo("Title: <A nice article> (420 words) By Kai")
}

When we run the test, it passes. So, the article object works as expected.

Apart from abstract classes, we can create anonymous objects from interfaces using a similar syntax. Next, let’s see how it works.

4. Creating Anonymous Objects From an Interface

In Kotlin, as interfaces cannot have constructors, the object expression’s syntax changes to:

object : TheInterface { ... implementations ... }

Next, let’s see an example. First, let’s say we have an interface:

interface Printable {
    val content: String
    fun print(): String
}

Next, let’s create an anonymous object from it:

val sentence = object : Printable {
    override val content: String = "A beautiful sentence."
    override fun print(): String = "[Print Result]\n$content"
}

As the code above shows, our anonymous class has overridden the content property and implemented the print function.

A test can verify that the sentence object works as expected:

sentence.let {
    assertThat(it).isInstanceOf(Printable::class.java)
    assertThat(it.print()).isEqualTo("[Print Result]\nA beautiful sentence.")
}

Next, let’s have a look at another interesting use case of anonymous objects.

5. Defining an Anonymous Object From Scratch

So far, we’ve explored creating anonymous objects of a supertype. Apart from that, in Kotlin, we can still define an anonymous object from scratch. Let’s first look at an example:

val player = object {
    val name = "Kai"
    val gamePlayed = 6L
    val points = 42L
    fun pointsPerGame() = "$name: AVG points per Game: ${points / gamePlayed}"
}

player.let {
    assertThat(it.name).isEqualTo("Kai")
    assertThat(it.pointsPerGame()).isEqualTo("Kai: AVG points per Game: 7")
}

As we can see in the code above, the player variable holds an anonymous object that doesn’t have an explicit supertype.

The object expression to create such an object is pretty simple this time: object { … implementations …}.

Many may think this is something new in Kotlin. We don’t have a counterpart in Java since, in Java, our anonymous class must have a supertype.

Actually, since every class in Java is a subclass of the Object class, the object expression works quite similar to this Java code:

Object player = new Object() {
    String name = "Kai";
    Long gamePlayed = 6L;
    String pointsPerGame() {
        return "$name: AVG points per Game: ${points / gamePlayed}"
    }
};

However, if we’ve defined the player object with the type Object, we cannot access player‘s properties or methods. However, If our Java version is 10 or newer, we can use the var keyword to define the player object. Then we can access its members in the same method:

var player = new Object() {
    String name = "Kai";
   ...
    String pointsPerGame() { ...}
};

player.pointsPerGame();
String playerName = player.name;

In Kotlin, we can use an anonymous object as the return value of a method. Moreover, if the anonymous object is returned by a private method, we can still access its members:

class PlayerService() {
    private fun giveMeAPlayer() = object {
        val name = "Kai"
        val gamePlayed = 6L
        val points = 42L
        fun pointsPerGame() = "$name: AVG points per Game: ${points / gamePlayed}"
    }

    fun getTheName(): String {
        val thePlayer = giveMeAPlayer()
        print(thePlayer.pointsPerGam())
        return thePlayer.name
    }
}

On the other side, Java doesn’t have this feature. This is because if a Java method returns a similar anonymous object, the method must define Object as the return type. Thus, we cannot access the anonymous object’s members.

6. Type Casting With Anonymous Objects

Type casting is a fundamental concept in Kotlin. It allows us to convert objects from one data type to another. In this section, let’s discuss type casting with anonymous objects.

6.1. Anonymous Objects with a Supertype

As shown earlier, we can create an anonymous object from an abstract class or an interface. In other words, the abstract class or the interface servers as the supertype of the anonymous object. In this scenario, there’s no need to cast the anonymous object to its supertype explicitly.

An example can explain it quickly. Let’s say we have a function that accepts a Doc object as the parameter:

fun docTitleToUppercase(doc: Doc) = doc.title.uppercase()

Here, we are using the abstract class Doc mentioned earlier.

Next, let’s create an anonymous object from the abstract class Doc:

val article = object : Doc(title = "A nice article", author = "Kai", words = 420) {
    override fun summary() = "Title: <$title> ($words words) By $author"
}

Then, we can directly pass the anonymous object article as the parameter to docTitleToUppercase() without explicit casting:

assertThat(docTitleToUppercase(article)).isEqualTo("A NICE ARTICLE")

This works because article is already an instance of the type Doc.

6.2. Anonymous Objects Defined From Scratch

Additionally, we have demonstrated that an anonymous object can be defined from scratch. In this scenario, the anonymous object’s type is Any. Moreover, casting it directly to any other type will raise an exception even if the target class has the same structure as the anonymous object.

Again, let’s understand it through an example. Let’s say we have a data class called Player:

data class Player(
    val name: String,
    val gamePlayed: Long,
    val points: Long
) {
    fun pointsPerGame() = "$name: AVG points per Game: ${points / gamePlayed}"
}

Next, let’s create an anonymous object from scratch:

val anonymousPlayer = object {
    val name = "Kai"
    val gamePlayed = 6L
    val points = 42L
    fun pointsPerGame() = "$name: AVG points per Game: ${points / gamePlayed}"
}

As we can see, the Player class and the anonymous object anonymousPlayer have the same structure.

Now, if we attempt to cast anonymousPlayer to Player, it throws ClassCastException:

assertFailsWith<ClassCastException> { anonymousPlayer as Player }

If we use Kotlin’s safe cast operator (as?) to perform the casting, the result is always null:

val alwaysNull = anonymousPlayer as? Player
assertThat(alwaysNull).isNull()

In certain programming languages, such as Python, when objects of different types have identical function definitions, we can treat these objects as if they belong to the same type, allowing for the use of common functions. This feature is referred to as duck typing. An example of Kotlin code written in the style of duck typing is as follows:

val realPlayer = Player(name = "Liam", gamePlayed = 7L, points = 77L)
 
val stringList = listOf(realPlayer, anonymousPlayer).map{
   it.pointsPerGame()
}

In this example, we created a Player instance and put it together with anonymousPlayer in a List. Since both annoymousPlayer and Player support the pointsPerGame() function, we expect to transform this List to a List of String values using duck typing and map().

However, Kotlin and Java are statically typed programming languages. They don’t support duck typing, so, the code above won’t compile.

Since we add two objects of unrelated types to a List, the List becomes List. Consequently, in the map() block, “it” is treated as Any. Naturally, the compiler complains that pointsPerGame() isn’t defined in Any.

Hence, if we know the exact type we want to work with, particularly if we intend to pass an object of that type for further processing outside the class, it’s preferable to create or return an instance of the required type directly rather than providing an anonymous object created from scratch.

7. Conclusion

In this article, we’ve addressed how to instantiate an abstract class or interface using object expressions in Kotlin.

Further, we’ve compared the anonymous objects in Kotlin and Java, especially when we define an anonymous object from scratch in Kotlin.

Finally, we discussed type casting on Kotlin anonymous objects.

As always, the full source code used in the article can be found over on GitHub.