问题
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?
回答1:
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")
回答2:
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.
来源:https://stackoverflow.com/questions/43030408/multiple-constructors-on-an-immutable-data-class