Multiple constructors on an immutable (data) class

和自甴很熟 提交于 2019-12-12 21:14:47

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!