Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple constructors on an immutable (data) class

I'm trying to implement an immutable data class with more than one constructor. I felt that something like this should be possible:

data class Color(val r: Int, val g: Int, val b: Int) {
   constructor(hex: String) {
        assert(Regex("#[a-fA-F0-6]{6}").matches(hex), { "$hex is not a hex color" } )
        val r = hex.substring(1..2).toInt(16)
        val g = hex.substring(3..4).toInt(16)
        val b = hex.substring(5..6).toInt(16)
        this(r,g,b)
    }
}

Of course, it isn't: Kotlin expects the call to the main constructor be declared at the top:

constructor(hex: String): this(r,g,b) {
    assert(Regex("#[a-fA-F0-6]{6}").matches(hex), { "$hex is not a hex color" } )
    val r = hex.substring(1..2).toInt(16)
    val g = hex.substring(3..4).toInt(16)
    val b = hex.substring(5..6).toInt(16)
}

That's no good either, as the call is executed before the constructor body and can not access the local variables.

I can do this, of course:

constructor(hex: String): this(hex.substring(1..2).toInt(16),
                               hex.substring(3..4).toInt(16), 
                               hex.substring(5..6).toInt(16)) {
    assert(Regex("#[a-fA-F0-6]{6}").matches(hex), { "$hex is not a hex color" } )
}

But this will check the assertion too late, and does not scale very well.

The only way I see to get close to the desired behaviour is this using a helper function (which can't be defined non-static on Color):

constructor(hex: String): this(hexExtract(hex, 1..2), 
                               hexExtract(hex, 3..4), 
                               hexExtract(hex, 5..6))

This does not strike me as a very elegant pattern, so I'm guessing I'm missing something here.

Is there an elegant, idiomatic way to have (complex) secondary constructors on immutable data classes in Kotlin?

like image 387
Raphael Avatar asked Sep 24 '26 14:09

Raphael


1 Answers

As suggested by @nhaarman, one way to is use a factory method. I often use something like the following:

data class Color(val r: Int, val g: Int, val b: Int) {
   companion object {
        fun fromHex(hex: String): Color {
            assert(Regex("#[a-fA-F0-6]{6}").matches(hex), { "$hex is not a hex color" } )
            val r = hex.substring(1..2).toInt(16)
            val g = hex.substring(3..4).toInt(16)
            val b = hex.substring(5..6).toInt(16)
            return Color(r,g,b)
        }
    }
}

And then you can call it with Color.fromHex("#abc123")

like image 181
ean5533 Avatar answered Sep 27 '26 05:09

ean5533