Is there a way to require a generic type to be a data class in Kotlin?
You also can implement copy or component1, component2 more generalised way.
For example:
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
}
}
I have the feeling that what you actually want is that T should be able to copy itself with a new ID, and have an ID. Not necessarily that it is a data class. So you could just use an interface to define that.
For example:
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
}
}
No, data
classes don't have any specific representation in type system and cannot be distinguished from regular classes (similar question).
You can, however, require the methods a data
class with certain number of components has using an interface (actually it will be a marker interface on data
classes).
Here's an example for data
classes with two components:
interface Data2<T1, T2> {
operator fun component1(): T1
operator fun component2(): T2
fun copy(t1: T1, t2: T2): Data2<T1, T2>
}
toString
, hashCode
and equals
can be called on any type anyway.
Then just mark your data
class with the interface:
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
function is not completely type-safe because Kotlin doesn't have self type (and no way to require interface implementations to be subtype of a specific type), but if you only mark your data
classes with it, it should work (see another option below).
Another drawback is that you lose default parameters of copy
method and have to call it with all the parameters specified:
val d = myD2.copy(newValue, myD2.component2())
Another option is to define these interfaces as Data2<T1, T2, out Self>
, class Impl(...): Data2<..., Impl>
, and make copy
return Self
, but it won't make it any better if you use the interface as Data2<SomeType, SomeType, *>
.