有没有办法在 Kotlin 中要求泛型类型成为数据 class?
Is there a way to require a generic type to be a data class in Kotlin?
以下内容无效,但希望能帮助您理解我的意思:
class Example<T : DataClass>
如果您想知道我想要完成什么,这是我想到的示例:
class Repository<T> where T : Entity, // Entity defines mutable property 'id'
T : DataClass {
// assume there is a map here
fun add(obj: T) {
val copy = obj.copy(id = generateID())
map.put(copy.id, copy)
}
}
或者是否有更好的方法来完成我想做的事情?
不,data
classes 在类型系统中没有任何特定的表示,无法与常规 classes () 区分开来。
但是,您可以要求具有一定数量组件的方法 data
class 使用接口(实际上它将是 data
[=52 上的标记接口=]es)。
这里有一个 data
class 有两个组件的例子:
interface Data2<T1, T2> {
operator fun component1(): T1
operator fun component2(): T2
fun copy(t1: T1, t2: T2): Data2<T1, T2>
}
toString
、hashCode
和 equals
可以在任何类型上调用。
然后用界面标记你的data
class:
data class Impl(val i: Int, val s: String): Data2<Int, String>
val d: Data2<Int, String> = Impl(1, "2")
val (c1, c2) = d
val copy = d.copy(-1, d.component2())
copy
函数不是完全类型安全的,因为 Kotlin (并且没有办法要求接口实现是特定类型的子类型),但是如果你只标记你的 data
class 是的,它应该可以工作(见下面的另一个选项)。
另一个缺点是您丢失了 copy
方法的默认参数并且必须使用指定的所有参数调用它:
val d = myD2.copy(newValue, myD2.component2())
另一种选择是将这些接口定义为Data2<T1, T2, out Self>
、class Impl(...): Data2<..., Impl>
,并使copy
returnSelf
,但它如果您将界面用作 Data2<SomeType, SomeType, *>
.
也不会让它变得更好
我感觉你真正想要的是T应该可以用一个新的ID复制自己,并且有一个ID。不一定是数据class。所以你可以只使用一个接口来定义它。
例如:
interface CopyableWithId<out T> where T: CopyableWithId<T> {
fun copy(newId: Long): T
val id: Long
}
data class BarBaz(override var id: Long, var name: String): CopyableWithId<BarBaz> {
override fun copy(newId: Long): BarBaz = copy(id = newId)
}
class Repository<T> where T : CopyableWithId<T>{
val map: MutableMap<Long, CopyableWithId<T>> = HashMap()
fun add(obj: T) {
val copy = obj.copy(generateID())
map.put(copy.id, copy)
}
private fun generateID(): Long {
return 1L
}
}
你也可以实现copy或者component1,component2更通用的方式。
例如:
interface Copyable <T> {
fun copy(fields: T.() -> T): T
}
data class BarBaz(var id: Long, var name: String): Copyable<BarBaz> {
override fun copy(fields: BarBaz.() -> BarBaz): BarBaz {
val instance = fields(this)
return copy(id = instance.id, name = instance.name)
}
}
class Repository<T> where T : Copyable<T>{
val map: MutableMap<Long, Copyable<T>> = HashMap()
fun add(obj: T) {
val copy = obj.copy{id = generateID()}
map.put(copy.id, copy)
}
private fun generateID(): Long {
return 1L
}
}
可能是无关的,因为我有类似但略有不同的问题。
我需要将共享逻辑移至超级 class,问题是我无法使用通用 T 的 copy 方法。我找到了这个解决方法:
实体:
data class MyEntity(
val id: String,
val createdAt: Instant,
val updatedAt: Instant
)
抽象通用存储库:
abstract class GenericRepository<T> {
abstract val copyFn: KCallable<T>
fun add(obj: T) {
val instanceParameter = copyFn.instanceParameter!!
val idParameter = copyFn.findParameterByName("id")!!
val copy = copyFn.callBy(
mapOf(
instanceParameter to obj,
idParameter to "new id"
)
)
// Do whatever you want with the copy
}
}
或者更简洁、更通用的抽象通用存储库版本:
abstract class BetterGenericRepository<T> {
abstract val copyFn: KCallable<T>
fun add(obj: T): T {
val instanceParameter = getInstanceParameter()
val idParameter = getParameterByName(instanceParameter, "id")
val updatedAtParameter = getParameterByName(instanceParameter, "updatedAt")
val copy = copyFn.callBy(
mapOf(
instanceParameter to obj,
idParameter to "new id",
updatedAtParameter to Instant.now()
)
)
// Do whatever you want with the copy
return copy
}
private fun getInstanceParameter() =
copyFn.instanceParameter
?: throw RuntimeException("${copyFn.returnType} must be Data Class or its method '${copyFn.name}' must have 'instanceParameter' as KParameter")
private fun getParameterByName(instanceParameter: KParameter, name: String) =
copyFn.findParameterByName(name)
?: throw RuntimeException("${instanceParameter.type} must have '$name' property")
}
Abstract Repository的具体实现
class MyRepository: BetterGenericRepository<MyEntity>() {
override val copyFn = MyEntity::copy
}
简单检查:
fun main() {
val repository = MyRepository()
val entity = MyEntity(
id = "1",
createdAt = Instant.EPOCH,
updatedAt = Instant.EPOCH
)
println(entity)
println(repository.add(entity))
}
结果
MyEntity(id=1, createdAt=1970-01-01T00:00:00Z, updatedAt=1970-01-01T00:00:00Z)
MyEntity(id=new id, createdAt=1970-01-01T00:00:00Z, updatedAt=2020-08-26T13:29:42.982Z)
以下内容无效,但希望能帮助您理解我的意思:
class Example<T : DataClass>
如果您想知道我想要完成什么,这是我想到的示例:
class Repository<T> where T : Entity, // Entity defines mutable property 'id'
T : DataClass {
// assume there is a map here
fun add(obj: T) {
val copy = obj.copy(id = generateID())
map.put(copy.id, copy)
}
}
或者是否有更好的方法来完成我想做的事情?
不,data
classes 在类型系统中没有任何特定的表示,无法与常规 classes (
但是,您可以要求具有一定数量组件的方法 data
class 使用接口(实际上它将是 data
[=52 上的标记接口=]es)。
这里有一个 data
class 有两个组件的例子:
interface Data2<T1, T2> {
operator fun component1(): T1
operator fun component2(): T2
fun copy(t1: T1, t2: T2): Data2<T1, T2>
}
toString
、hashCode
和 equals
可以在任何类型上调用。
然后用界面标记你的data
class:
data class Impl(val i: Int, val s: String): Data2<Int, String>
val d: Data2<Int, String> = Impl(1, "2")
val (c1, c2) = d
val copy = d.copy(-1, d.component2())
copy
函数不是完全类型安全的,因为 Kotlin data
class 是的,它应该可以工作(见下面的另一个选项)。
另一个缺点是您丢失了 copy
方法的默认参数并且必须使用指定的所有参数调用它:
val d = myD2.copy(newValue, myD2.component2())
另一种选择是将这些接口定义为Data2<T1, T2, out Self>
、class Impl(...): Data2<..., Impl>
,并使copy
returnSelf
,但它如果您将界面用作 Data2<SomeType, SomeType, *>
.
我感觉你真正想要的是T应该可以用一个新的ID复制自己,并且有一个ID。不一定是数据class。所以你可以只使用一个接口来定义它。
例如:
interface CopyableWithId<out T> where T: CopyableWithId<T> {
fun copy(newId: Long): T
val id: Long
}
data class BarBaz(override var id: Long, var name: String): CopyableWithId<BarBaz> {
override fun copy(newId: Long): BarBaz = copy(id = newId)
}
class Repository<T> where T : CopyableWithId<T>{
val map: MutableMap<Long, CopyableWithId<T>> = HashMap()
fun add(obj: T) {
val copy = obj.copy(generateID())
map.put(copy.id, copy)
}
private fun generateID(): Long {
return 1L
}
}
你也可以实现copy或者component1,component2更通用的方式。
例如:
interface Copyable <T> {
fun copy(fields: T.() -> T): T
}
data class BarBaz(var id: Long, var name: String): Copyable<BarBaz> {
override fun copy(fields: BarBaz.() -> BarBaz): BarBaz {
val instance = fields(this)
return copy(id = instance.id, name = instance.name)
}
}
class Repository<T> where T : Copyable<T>{
val map: MutableMap<Long, Copyable<T>> = HashMap()
fun add(obj: T) {
val copy = obj.copy{id = generateID()}
map.put(copy.id, copy)
}
private fun generateID(): Long {
return 1L
}
}
可能是无关的,因为我有类似但略有不同的问题。
我需要将共享逻辑移至超级 class,问题是我无法使用通用 T 的 copy 方法。我找到了这个解决方法:
实体:
data class MyEntity(
val id: String,
val createdAt: Instant,
val updatedAt: Instant
)
抽象通用存储库:
abstract class GenericRepository<T> {
abstract val copyFn: KCallable<T>
fun add(obj: T) {
val instanceParameter = copyFn.instanceParameter!!
val idParameter = copyFn.findParameterByName("id")!!
val copy = copyFn.callBy(
mapOf(
instanceParameter to obj,
idParameter to "new id"
)
)
// Do whatever you want with the copy
}
}
或者更简洁、更通用的抽象通用存储库版本:
abstract class BetterGenericRepository<T> {
abstract val copyFn: KCallable<T>
fun add(obj: T): T {
val instanceParameter = getInstanceParameter()
val idParameter = getParameterByName(instanceParameter, "id")
val updatedAtParameter = getParameterByName(instanceParameter, "updatedAt")
val copy = copyFn.callBy(
mapOf(
instanceParameter to obj,
idParameter to "new id",
updatedAtParameter to Instant.now()
)
)
// Do whatever you want with the copy
return copy
}
private fun getInstanceParameter() =
copyFn.instanceParameter
?: throw RuntimeException("${copyFn.returnType} must be Data Class or its method '${copyFn.name}' must have 'instanceParameter' as KParameter")
private fun getParameterByName(instanceParameter: KParameter, name: String) =
copyFn.findParameterByName(name)
?: throw RuntimeException("${instanceParameter.type} must have '$name' property")
}
Abstract Repository的具体实现
class MyRepository: BetterGenericRepository<MyEntity>() {
override val copyFn = MyEntity::copy
}
简单检查:
fun main() {
val repository = MyRepository()
val entity = MyEntity(
id = "1",
createdAt = Instant.EPOCH,
updatedAt = Instant.EPOCH
)
println(entity)
println(repository.add(entity))
}
结果
MyEntity(id=1, createdAt=1970-01-01T00:00:00Z, updatedAt=1970-01-01T00:00:00Z)
MyEntity(id=new id, createdAt=1970-01-01T00:00:00Z, updatedAt=2020-08-26T13:29:42.982Z)