Secondary constructor vs primary constructor with defaults

Again, I’m a noob (like, really) to OOP and Kotlin.

#1) After recently studying and reading on constructors (primary vs secondary), I thought the concept was pretty cool–giving the option to instantiate an object of a class using a different number of parameters.
However, it now occurs to me that you can accomplish exactly the same thing by using defaults in the primary constructor.

What am I missing? What does a secondary constructor add that a primary constructor with defaults can’t do?

#2) Another thing I feel hung up on is the syntax of the secondary constructor. Here is an example from the Big Nerd Ranch Guide for Kotlin Programming:

class Player (_name: String, var healthPoints: Int, var isBlessed: Boolean, private val isImmortal: Boolean) {
    var name = _name
        get() = field.capitalize()
        private set(value) {
            field = value.trim()
        }

    constructor(name: String) : this(name, healthPoints = 100, isBlessed = true, isImmortal = false)
}

The secondary constructor must reference the primary, and it does so by using this. It appears that any parameter of the primary constructor that is not included in the secondary constructor has to be referenced in the this block, using the exact same parameter names as those in the primary constructor, is this correct? For example, I wouldn’t be able to rename the healthPoints parameter in the secondary constructor’s this block to something else (ie: totalHealth), and expect that this will convey the correct information to the primary constructor based solely on the parameter’s type and location in the order of parameters?

However, the parameter(s) that are passed in via the secondary constructor must be used in the this block exactly as they are in the secondary constructor and NOT necessarily the same as the primary constructor, is this correct? In the above code, the primary constructor’s first parameter is _name, whereas the name of the parameter in the secondary constructor is name. The secondary constructor’s name parameter is what is passed in the this block (as name), rather than _name.

1 Like

Secondary constructors usually aren’t used for having default arguments in Kotlin. Their use is mostly to unwrap a certain value or allow a special way to initialize the class. Building a bit upon the Bin Nerd Ranch example, let’s say that you have a User class which is defined like this:
data class User (val id : Long, val name: String, val permanentBuffs: List<Buff>)
and a Buff class defined like this:
data class Buff (val name: String, val value: Int)
(In other words, a User is basically the global profile of the person that’s playing the game).
Then let’s assume that we have that same Player class from that example (and let’s assume that a Player is basically the current data that relates to the Game that the user is playing i.e. a Player only lasts for one session or one turn, while a User is persistent). We want to make creating a player easy, so we want to allow creating it from a User object. The best way to do that would be something along the lines of:

constructor(user: User) : this(user.name, user.permanentBuffs.first { it.name == "healthPoints" }.value, user.permanentBuffs.first { it.name == "isBlessed" }.value > 0, user.permanentBuffs.first { it.name == "isImmortal" }.value > 0)

(This example can definitely have a lot of improvements to allow better type safety, but I’m just trying to showcase that secondary constructors can help in extracting the values that you need out of other data)
Regarding the naming question, parameter names are not required in Kotlin when you are calling a function, so just don’t overthink them too much. Constructors are basically just like regular functions w.r.t naming, so there’s nothing special about how naming works for a secondary constructor. In general, you can use the parameter names that a function specifies when you are calling it, and you can define whatever arbritary parameter names that you want when defining your own function (which is why the secondary constructor in the initial example can have a parameter called name, it is completely arbritary). BTW, in that initial example, you could’ve just had this instead and it’ll result in the same exact thing:
constructor(name: String) : this(name, 100, true, false)

1 Like

Thanks for the reply. This is a very great example that helps me understand secondary constructors much better! I really appreciate it.

And thanks for helping me realize calling a secondary constructor and referencing a primary constructor is just like calling any other function, with named parameters, etc. Seems kind of obvious now.

1 Like

Secondary constructors I believe are largely only in the language for interoperability with Java; I’ve never found a use for one in Kotlin and I don’t believe their usage is idiomatic.

@kyay10’s example above is quite illustrative of how secondary constructors work, but a more idiomatic way to solve the example problem would be to use an extension function:

fun User.toPlayer(): Player {
  return User(name, permanentBuffs.first { it.name == "healthPoints" }.value, permanentBuffs.first { it.name == "isBlessed" }.value > 0, permanentBuffs.first { it.name == "isImmortal" }.value > 0)
}

By putting that method in Player.kt, the User class doesn’t need to know that players exist, but any code that has access to Player can also convert users to players

2 Likes