更新:从评论中添加了一些说明
我想对主要构造函数使用相同的“映射”代码,并使用不可变copy()
的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
)
}
}
虽然可变性很好,但就我而言,我有兴趣知道这是可能的。
避免将属性列出4次的一种方法是将Employee
声明为接口,并使用“可变”版本(表单)作为实现它的唯一数据类。您可以使用该界面获得“只读”视图,但从技术上讲,您只能在幕后使用可变实例。
这将遵循Kotlin设计师为List
和MutableList
所做的事情。
interface Employee {
val firstName: String
val lastName: String
val jobType: Int
}
data class EmployeeForm(
override var firstName: String = "",
override var lastName: String = "",
override var jobType: Int = 0
): Employee {
fun toEmployee(): Employee = this.copy()
fun copyEmployee(employee: Employee): Employee = this.copy(
firstName = firstName,
lastName = lastName,
jobType = jobType
)
}
但是,这意味着表单包含员工的所有字段,您可能不需要。
另外,我个人更喜欢你在开始时所做的事情,列出两次字段不会有问题,只需为你的函数编写测试,当你想要添加功能时,你也会为这个功能添加测试。
您应该能够使用反射执行此操作:检查Employee
和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中是可能的。