有没有办法要求泛型类型成为Kotlin中的数据类?

时间:2016-06-05 16:22:19

标签: generics kotlin data-class

以下不起作用,但希望能帮助您理解我的意思:

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)
  }

}

还是有更好的方法来完成我想做的事情吗?

4 个答案:

答案 0 :(得分:6)

我觉得你真正想要的是T应该能够用一个新ID复制自己,并且有一个ID。不一定是数据类。所以你可以使用一个接口来定义它。

例如:

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
    }
}

答案 1 :(得分:1)

不,data类在类型系统中没有任何特定的表示形式,无法与常规类(similar question)区分开来。

但是,您可以要求具有特定数量组件的data类使用接口的方法(实际上它将是data类上的标记接口)。

以下是包含两个组件的interface Data2<T1, T2> { operator fun component1(): T1 operator fun component2(): T2 fun copy(t1: T1, t2: T2): Data2<T1, T2> } 类的示例:

toString
无论如何,

hashCodeequalsdata都可以在任何类型上调用。

然后使用界面标记您的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

data函数不是完全类型安全的,因为Kotlin doesn't have self type(并且无法将接口实现作为特定类型的子类型),但如果您只标记copy使用它的类,它应该工作(见下面的另一个选项)。

另一个缺点是您丢失了val d = myD2.copy(newValue, myD2.component2()) 方法的默认参数,并且必须使用指定的所有参数调用它:

Data2<T1, T2, out Self>

另一种选择是将这些界面定义为class Impl(...): Data2<..., Impl>copy,并使Self返回Data2<SomeType, SomeType, *>,但它不会使其成为任何界面如果您将界面用作{{1}},那就更好了。

答案 2 :(得分:0)

您还可以更通用地实现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
    }
}

答案 3 :(得分:0)

可能不相关,因为我遇到了类似但略有不同的问题。

我需要将共享逻辑转移到超类中,问题是我不能使用通用 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)