tutorial, no_image, kotlin,

Kotlin - no_image

Upendra Upendra Follow Jan 23, 2025 · 6 mins read
Kotlin - no_image
Share this

object keyword

The object keyword allow you to define a class and create an instance of it in a single step. This is useful when you need either a reusable singleton instance or a one-time object. Kotlin provides two key approaches for the object keyword:

  • object declarations for creating singletons;
  • object expressions for creating anonymous, one-time objects.1

Object declarations

You can create single instances of objects in Kotlin using object declarations, which always have a name following the object keyword. This allows you to define a class and create an instance of it in a single step, which is useful for implementing singletons:

// Declares a Singleton object to manage data providers
object DataProviderManager {
    private val providers = mutableListOf<DataProvider>()

    // Registers a new data provider
    fun registerDataProvider(provider: DataProvider) {
        providers.add(provider)
    }

    // Retrieves all registered data providers
    val allDataProviders: Collection<DataProvider> 
        get() = providers
}

To refer to the object, use its name directly:

DataProviderManager.registerDataProvider(exampleProvider)

Object declarations can also have supertypes, similar to how anonymous objects can inherit from existing classes or implement interfaces:

object DefaultListener : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) { ... }

    override fun mouseEntered(e: MouseEvent) { ... }
}

Like variable declarations, object declarations are not expressions, so they cannot be used on the right-hand side of an assignment statement:

// Syntax error: An object expression cannot bind a name.
val myObject = object MySingleton {
val name = "Singleton"
}

Object declarations cannot be local, which means they cannot be nested directly inside a function. However, they can be nested within other object declarations or non-inner classes.

Note: The initialization of an object declaration is thread-safe and done on first access.

Object expressions

Object expressions declare a class and create an instance of that class, but without naming either of them. These classes are useful for one-time use. They can either be created from scratch, inherit from existing classes, or implement interfaces. Instances of these classes are also called anonymous objects because they are defined by an expression, not a name.

Create anonymous objects from scratch

Object expressions start with the object keyword.

If the object doesn’t extend any classes or implement interfaces, you can define an object’s members directly inside curly braces after the object keyword:

val helloWorld = object {
    val hello = "Hello"
    val world = "World"
    // Object expressions extend the Any class, which already has a toString() function,
    // so it must be overridden
    override fun toString() = "$hello $world"
}

print(helloWorld)
// Hello World

Inherit anonymous objects from supertypes

To create an anonymous object that inherits from some type (or types), specify this type after object and a colon :. Then implement or override the members of this class as if you were inheriting from it:

window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) { /*...*/ }

    override fun mouseEntered(e: MouseEvent) { /*...*/ }
})

If a supertype has a constructor, pass the appropriate constructor parameters to it. Multiple supertypes can be specified, separated by commas, after the colon:

// Creates an open class BankAccount with a balance property
open class BankAccount(initialBalance: Int) {
    open val balance: Int = initialBalance
}

// Defines an interface Transaction with an execute() function
interface Transaction {
    fun execute()
}

// A function to perform a special transaction on a BankAccount
fun specialTransaction(account: BankAccount) {
    // Creates an anonymous object that inherits from the BankAccount class and implements the Transaction interface
    // The balance of the provided account is passed to the BankAccount superclass constructor
    val temporaryAccount = object : BankAccount(account.balance), Transaction {

        override val balance = account.balance + 500  // Temporary bonus

        // Implements the execute() function from the Transaction interface
        override fun execute() {
            println("Executing special transaction. New balance is $balance.")
        }
    }
    // Executes the transaction
    temporaryAccount.execute()
}

Use anonymous objects as return and value types

When you return an anonymous object from a local or private function or property (but not an inline function), all the members of that anonymous object are accessible through that function or property:

class UserPreferences {
    private fun getPreferences() = object {
        val theme: String = "Dark"
        val fontSize: Int = 14
    }

    fun printPreferences() {
        val preferences = getPreferences()
        println("Theme: ${preferences.theme}, Font Size: ${preferences.fontSize}")
    }
}

This allows you to return an anonymous object with specific properties, offering a simple way to encapsulate data or behavior without creating a separate class.

If a function or property that returns an anonymous object is public or private, its actual type is:

  • Any if the anonymous object doesn’t have a declared supertype;
  • The declared supertype of the anonymous object, if there is exactly one such type;
  • The explicitly declared type if there is more than one declared supertype.

In all these cases, members added in the anonymous object are not accessible. Overridden members are accessible if they are declared in the actual type of the function or property.

Access variables from anonymous objects

Code within the body of object expressions can access variables from the enclosing scope:

import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent

fun countClicks(window: JComponent) {
    var clickCount = 0
    var enterCount = 0

    // MouseAdapter provides default implementations for mouse event functions
    // Simulates MouseAdapter handling mouse events
    window.addMouseListener(object : MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {
            clickCount++
        }

        override fun mouseEntered(e: MouseEvent) {
            enterCount++
        }
    })
    // The clickCount and enterCount variables are accessible within the object expression
}

Behavior difference between object declarations and expressions

There are differences in the initialization behavior between object declarations and object expressions:

  • Object expressions are executed (and initialized) immediately, where they are used;
  • Object declarations are initialized lazily, when accessed for the first time;
  • A companion object is initialized when the corresponding class is loaded (resolved) that matches the semantics of a Java static initializer.

Links

Object declarations and expressions

Next questions

What do you know about companion object?

Further reading

Understanding the Object Keyword in Kotlin

Objects in Kotlin: Create safe singletons in one line of code (KAD 27)

Object keyword in kotlin

credit goes to @swayangjit
Join Newsletter
Get the latest news right in your inbox. We never spam!
Upendra
Written by Upendra Follow
Hi, I am Upendra, the author in Human and machine languages,I don't know to how 3 liner bio works so just Connect with me on social sites you will get to know me better.