我有一个Kotlin数据类,我正在构建许多不可变属性,这些属性是从单独的SQL查询中获取的。如果我想使用构建器模式构造数据类,如何在不使这些属性可变的情况下执行此操作?
例如,而不是构建通过
var data = MyData(val1, val2, val3)
我想用
builder.someVal(val1)
// compute val2
builder.someOtherVal(val2)
// ...
var data = builder.build()
同时仍使用Kotlin的数据类功能和不可变属性。
答案 0 :(得分:3)
我不认为Kotlin有本土建设者。您始终可以计算所有值并在最后创建对象。
如果您仍想使用构建器,则必须自行实现。查看this question
答案 1 :(得分:3)
我同意Grzegorz答案中的数据副本块,但它与使用构造函数创建数据类的语法基本相同。如果你想使用那种方法并保持一切清晰,你可能会事先计算所有内容并最终将所有值一起传递。
要拥有更像建筑师的东西,您可以考虑以下事项:
我们说你的数据类是
data class Data(val text: String, val number: Int, val time: Long)
您可以创建一个这样的可变构建器版本,使用构建方法来创建数据类:
class Builder {
var text = "hello"
var number = 2
var time = System.currentTimeMillis()
internal fun build()
= Data(text, number, time)
}
除了像这样的构建器方法:
fun createData(action: Builder.() -> Unit): Data {
val builder = Builder()
builder.action()
return builder.build()
}
Action是一个可以直接修改值的函数,createData
会在之后直接为它构建一个数据类。
这样,您可以使用以下命令创建数据类:
val data: Data = createData {
//execute stuff here
text = "new text"
//calculate number
number = -1
//calculate time
time = 222L
}
每个说法没有setter方法,但您可以直接为可变变量分配新值,并在构建器中调用其他方法。
你也可以通过为每个变量指定你自己的函数来使用kotlin的get和set,这样它就可以做更多的事情而不是设置字段。
也不需要返回当前的构建器类,因为您始终可以访问其变量。
补充说明:如果您在意,createData
可以缩短为:
fun createData(action: Builder.() -> Unit): Data = with(Builder()) { action(); build() }.
"使用新的构建器,应用我们的操作并构建"
答案 2 :(得分:1)
不需要在Kotlin中创建自定义构建器 - 为了实现类似构建器的语义,您可以使用copy
方法 - 它非常适合您想要获取对象的情况。副本稍有改动。
data class MyData(val val1: String? = null, val val2: String? = null, val val3: String? = null)
val temp = MyData()
.copy(val1 = "1")
.copy(val2 = "2")
.copy(val3 = "3")
或者:
val empty = MyData()
val with1 = empty.copy(val1 = "1")
val with2 = with1.copy(val2 = "2")
val with3 = with2.copy(val3 = "3")
由于您希望所有内容都是不可变的,因此必须在每个阶段进行复制。
此外,只要构造的结果是不可变的,在构建器中具有可变属性就没有问题。
答案 3 :(得分:1)
可以使用注释处理器机械化创建构建器类。 我刚刚创建ephemient/builder-generator来证明这一点。
请注意,目前,kapt适用于生成的Java代码,但生成的Kotlin代码存在一些问题(请参阅KT-14070)。出于这些目的,这不是一个问题,只要将可空性注释从原始Kotlin类复制到生成的Java构建器(使得使用生成的Java代码的Kotlin代码看到可空/非可空类型而不是只是平台类型)。