Skip to Content
Course content

18: Primary and Secondary Constructors

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

In most languages you've used, the constructor is a separate block of code—a function with the same name as the class. Kotlin does things differently. It pushes the primary constructor right into the class header, which makes the code incredibly concise, but it can feel a bit weird when you suddenly need another way to initialize your object.

Let's build a GameCharacter class. In a real game, you usually have a standard way to create a character, but you might also need to load one from a save file or a database string. This is the perfect scenario to show why we need both primary and secondary constructors.

The streamlined primary approach

Most of the time, you'll stop right here. The primary constructor is defined immediately after the class name. I'm going to define my character with a name, a level, and some experience points. By adding val or var inside the parentheses, I'm telling Kotlin to actually create those properties for me so I don't have to declare them again inside the class body.

class GameCharacter(val name: String, var level: Int, var xp: Int) {
    fun displayStats() {
        println("$name - Level $level ($xp XP)")
    }
}

This is clean. If I call GameCharacter("Aragorn", 10, 1200), it just works. But what if my save data is stored as a single comma-separated string, like "Legolas,12,2500"? I don't want the rest of my app to have to manually split that string every single time I create a character.

Handling a messy save string

This is where a secondary constructor comes in. I want a way to pass in a String, parse it, and then create the character. In Kotlin, secondary constructors are marked with the constructor keyword inside the class body.

class GameCharacter(val name: String, var level: Int, var xp: Int) {
    // Secondary constructor for loading from a string
    constructor(saveData: String) {
        val parts = saveData.split(",")
        // I'll handle the logic here...
    }
    
    fun displayStats() {
        println("$name - Level $level ($xp XP)")
    }
}

The "Missing Delegate" trap

Now, if I try to compile the code above, the compiler is going to yell at me. I'll admit, I've made this mistake more times than I'd like to admit when switching from Java to Kotlin. I tried to just assign the values to this.name and this.level inside the secondary constructor.

Here is the rule: If a class has a primary constructor, every secondary constructor must delegate to it.

Kotlin wants to ensure that the primary constructor's initialization logic always runs. You can't just bypass it. To fix this, I need to use the this() keyword to call the primary constructor from within the secondary one.

Putting it all together

To make this work, I have to do the parsing before I call the primary constructor. Since this() must be the first thing called in the constructor body, I'll use a little trick: I'll handle the logic in a companion object or simply pass the result of a parsing function into this(). But for a simple example, let's just split the string and pass the parts directly.

class GameCharacter(val name: String, var level: Int, var xp: Int) {
    
    // The secondary constructor delegates to the primary one using 'this'
    constructor(saveData: String) : this(
        name = saveData.split(",")[0],
        level = saveData.split(",")[1].toInt(),
        xp = saveData.split(",")[2].toInt()
    )

    fun displayStats() {
        println("$name - Level $level ($xp XP)")
    }
}

fun main() {
    // Using the primary constructor
    val hero = GameCharacter("Gimli", 8, 900)
    
    // Using the secondary constructor
    val loadedHero = GameCharacter("Legolas,12,2500")
    
    hero.displayStats()      // Gimli - Level 8 (900 XP)
    loadedHero.displayStats() // Legolas - Level 12 (2500 XP)
}

Now we have the best of both worlds: a concise primary way to build our object, and a specialized secondary way to handle "dirty" input data.




📋 Practical Task

Building a Legacy ServerConfig Parser

You are working on a networking tool. The modern way to create a ServerConfig object is by passing the host (String) and port (Int) separately. However, the tool still needs to support old configuration files that provide the connection as a single string (e.g., "192.168.1.1:8080").

Your Task:

  • Create a class named ServerConfig.
  • Define a primary constructor that takes host: String and port: Int.
  • Define a secondary constructor that takes a single connectionString: String.
  • Inside the secondary constructor, split the connectionString by the colon (:) and delegate the results to the primary constructor using this().
  • Add a function printConfig() that prints: "Connecting to [host] on port [port]".

Test your code by creating one instance using the primary constructor and one using the secondary constructor, then calling printConfig() on both.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.