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?
As explained here, using the operator function invoke
on the companion object (just like Scala's apply
) one can achieve not really a constructor, but a factory that looks like a constructor usage-site:
companion object {
operator fun invoke(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)
}
}
Now, Color("#FF00FF")
will to the expected thing.