首页 > 解决方案 > 在 Kotlin 中为不可变数据类重用映射代码

问题描述

更新:从评论中添加了一些澄清

我想对copy()immutable 的主构造函数和方法使用相同的“映射”代码data class。我怎么能做到这一点而不先创建一个空对象,然后copy()在它上面使用?

现在的问题是,如果我添加一个具有默认值的新属性,Employee并且EmployeeForm很容易只将它添加到两个映射函数之一中而忘记另一个(toEmployeeNotReusable/ copyEmployee)。

这些是我想映射的数据类:

@Entity
data class Employee(
    val firstName: String,
    val lastName: String,
    val jobType: Int,

    @OneToMany(mappedBy = "employee", cascade = [CascadeType.ALL], fetch = FetchType.EAGER)
    private val _absences: MutableSet<Absence> = mutableSetOf(),

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long = 0 // prevents @Joffrey's answer from working
) {
    init {
        _absences.forEach { it.employee = this }
    }

    val absences get() = _absences.toSet()

    fun addAbsence(newAbsence: Absence) {
        newAbsence.employee = this
        _absences += newAbsence
    }

    @Entity
    @Table(name = "absence")
    data class Absence(
        // ... omitted fields
    ) {
        @ManyToOne(fetch = FetchType.EAGER)
        @JoinColumn(name = "employee_id")
        lateinit var employee: Employee
    }
}


data class EmployeeForm(
    var firstName: String = "",
    var lastName: String = "",
    var jobType: Int = 0
) {
    // not reusable
    fun toEmployeeNotReusable(): Employee {
        return Employee(firstName, lastName, jobType)
    }

    // works but hacky
    fun toEmployee(): Employee {
        return copyEmployee(Employee("", "", 0))
    }

    fun copyEmployee(employee: Employee): Employee {
        return employee.copy(
            firstName = firstName,
            lastName = lastName,
            jobType = jobType
        )
    }
}

虽然可变性很好,但就我而言,我很想知道这怎么可能。

标签: kotlinimmutability

解决方案


您应该能够使用反射来做到这一点:检查Employeeand中的属性列表,EmployeeForm通过匹配的名称调用构造函数(callBy用于处理默认参数)。当然,缺点是如果缺少任何属性,您将不会收到编译时错误(但在这种情况下,任何测试都可能会失败并告诉您问题所在)。

近似且未经测试(不要忘记添加kotlin-reflect依赖项):

inline fun <reified T> copy(x: Any): T {
    val construct = T::class.primaryConstructor
    val props = x::class.memberProperties.associate { 
        // assumes all properties on x are valid params for the constructor
        Pair(construct.findParameterByName(it.name)!!,
             it.call(x))
    }
    return construct.callBy(props)
}

// in EmployeeForm
fun toEmployee() = copy<Employee>(this)

您可以制作一个使用 Scala 宏在编译时检查的等效项,但我认为这在 Kotlin 中是不可能的。


推荐阅读