Skip to Content
Course content

22: Object Declarations and Companion Objects

Click on the "Edit" button in the top corner of the screen to edit your slide content.

In Java, if you wanted a Singleton, you had to write a bunch of boilerplate: a private constructor, a static instance variable, and a synchronized getter. It was tedious and easy to mess up. In Kotlin, we have the object keyword, which handles all of that in one go.

Creating a global settings hub

Let's imagine we're building a simple game. We need a place to store global configuration—things like the screen resolution or the volume level—that every other part of the game can access without us having to pass a settings object through twenty different constructors.

object GameSettings {
    var volume: Float = 0.8f
    var resolution: String = "1920x1080"
    var isMuted: Boolean = false

    fun resetToDefaults() {
        volume = 0.8f
        resolution = "1920x1080"
        isMuted = false
    }
}

By using object instead of class, I've told Kotlin that GameSettings is a Singleton. There is exactly one instance of this object in the entire application, and it's created lazily the first time it's accessed. I can just call GameSettings.volume = 0.5f from anywhere in my code. It's clean, and it's thread-safe by default.

Oops, I forgot how Singletons work

I'll be honest: even after years of doing this, when I'm typing fast, I sometimes slip back into "class mode." I just tried to do this in my IDE:

val mySettings = GameSettings() // Error: 'object' GameSettings cannot be instantiated

The compiler immediately yelled at me. You can't call a constructor on an object because there is no constructor for you to call—the language handles the instantiation behind the scenes. If I find myself wanting to create multiple instances of something, I need to change it back to a class. But for global config? The object declaration is exactly what we want.

Tying logic to the Player class

Now, what if I want some functionality that belongs to a class, but doesn't actually need a specific instance of that class to work? In Java, you'd use static. In Kotlin, we use a companion object.

Let's say we have a Player class. We want a way to create a "Default" player without manually filling in every property every time. I'll put a factory method inside a companion object:

class Player(val name: String, val level: Int) {
    
    companion object {
        const val MAX_LEVEL = 100
        
        fun createGuestPlayer(): Player {
            return Player("Guest_${(1..1000).random()}", 1)
        }
    }
}

The companion object is essentially an object tied to the Player class. Notice that I used const val for MAX_LEVEL. Because it's inside a companion object and marked as const, it's treated as a true compile-time constant, which is more efficient than a regular read-only property.

Now, when I'm in my main game loop, I can do this:

val p1 = Player("Hero", 10)
val p2 = Player.createGuestPlayer()

println("Max level is ${Player.MAX_LEVEL}")

I'm calling createGuestPlayer() and accessing MAX_LEVEL directly on the class name Player, not on an instance of a player. This keeps the namespace organized—anything related to the idea of a Player lives in the companion object, while anything related to a specific Player lives in the class body.




📋 Practical Task

Building a Database Connection Manager and User Factory

You need to implement a small system for managing a mock database connection and user creation. Complete the following requirements:

  • Create an object called DbConnection. It should have a property connectionString (set to "jdbc:mysql://localhost:3306/mydb") and a function connect() that prints "Connecting to [connectionString]...".
  • Create a User class with properties username and role (both Strings).
  • Inside the User class, add a companion object.
  • In that companion object, create a function createAdmin(name: String) that returns a User instance with the provided name and the role "Administrator".
  • In your main function:
    1. Call the connect() method using the DbConnection singleton.
    2. Create a new admin user using the User.createAdmin() factory method and print their role to the console.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.