如何为具有许多不可变属性的Kotlin数据类构建一个构建器

我有一个Kotlin数据类,我正在构建许多不可变的属性,这些属性是从单独的SQL查询中获取的。 如果我想使用构建器模式构造数据类,那么如何在不使这些属性可变的情况下做到这一点?

例如,而不是通过构建

var data = MyData(val1, val2, val3) 

我想用

 builder.someVal(val1) // compute val2 builder.someOtherVal(val2) // ... var data = builder.build() 

同时仍然使用Kotlin的数据类特性和不可变属性。

我不认为Kotlin有本地建设者。 您始终可以计算所有值并在最后创建对象。

如果你仍然想使用一个建设者,你将不得不自己实现它。 检查这个问题

我同意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方法,但是可以直接使用新值分配可变variables,并在构建器中调用其他方法。

你也可以利用kotlin的get和set来为每个variables指定你自己的函数,这样可以做的不仅仅是设置字段。

您也不需要返回当前的构建器类,因为您始终可以访问其variables。

补充说明:如果你在意, createData可以缩写为:

 fun createData(action: Builder.() -> Unit): Data = with(Builder()) { action(); build() }. 

“有了一个新的建设者,应用我们的行动,建立”

没有必要在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") 

既然你想要一切都是不变的,复制必须发生在每个阶段。

而且,只要构建器产生的结果是不可变的,就可以在构建器中具有可变属性。

使用注释处理器可以机械化构建器类的创建。 我刚创建了ephemient / builder-generator来演示这个。

请注意,目前,kapt对生成的Java代码工作良好,但生成的Kotlin代码存在一些问题(请参阅KT-14070 )。 出于这些目的,这不是问题,只要可空性注释从原始Kotlin类复制到生成的Java构建器(使得使用生成的Java代码的Kotlin代码看到可空/不可空的types而不是仅平台types)。