Reputation: 2195
I'm looking for a way in Kotlin to pass generics around - but they would have fixed types by the time of compile.
My goal would be to create a base class Field<T>
, which would have a public property of type T, which is backed by a ByteArray field.
The ByteArray would be converted on the run to and from T, using a global converter repository. However I can't wrap my head around Kotlin's type erasure, something I haven't met in C# (pretty much the same code would work wonderfully).
So, my altogether goal would be this class (note, this is mostly pseudocode!):
class Field<T> {
private var actualData: ByteArray = TODO("Init here")
public var Data: T =
get() = getConverter().convert(actualData)
set(value) {
val converted = getConverter().convertBack(value)
// Do some other nasty stuff here
actualData = converted
}
private fun getConverter(): Converter<T> = TODO("This needs implementation")
}
Since the conversion steps are always the same, it would be awesome if I could dynamically grab the converter and not need to make an inheriting class every time, i.e. I could define fields that "just work":
var data1: Field<Int> = TODO("Init here")
var data2: Field<MyOjbect> = TODO("Init here")
And of course I would like to avoid the implementation of getConverter()
for each type.
Is there a way to make this work in Kotlin, or should I stick to Java within this part?
(P.S.: I will obviously not name my class Field<T>
, but this name seemed to be the most generic one to describe its role in this scenario).
Upvotes: 0
Views: 722
Reputation: 17288
You can use reified type parameters, but it will never be compile-time safe and will carry runtime overhead when compared to explicitly creating delegates for each type.
If I understood your question and comment, you're looking for something like this:
Converter interface:
interface Converter<T>{
fun convert(value: T) : ByteArray
fun convertBack(byteArray: ByteArray) : T
}
Object (singleton) holding all converters:
import kotlin.reflect.KClass
object Converters{
val converters = HashMap<KClass<out Any>, Converter<out Any>>()
inline fun<reified T : Any> put(converter: Converter<T>){
converters[T::class] = converter
}
fun<T: Any> get(kclass : KClass<T>) : Converter<T>{
val converter = converters[kclass] ?: throw IllegalStateException("Missing converter for $kclass")
return converter as Converter<T>
}
init {
//add default converters?
put(object : Converter<Int>{
override fun convert(value: Int): ByteArray {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun convertBack(byteArray: ByteArray): Int {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
})
}
}
Class using converters, infline function above mimicks constructor but doesn't require explicit class argument when called:
inline fun<reified T : Any> ByteField(initialValue: T) = ByteField(initialValue, T::class)
class ByteField<T: Any>(initialValue: T, private val kclass: KClass<T>){
private var actualData = converter.convert(initialValue)
val converter
get() = Converters.get(kclass)
var data : T
get() = converter.convertBack(actualData)
set(value) {
actualData = converter.convert(value)
}
}
Usage demo (of course you can implement property delegates as well):
class Demo{
val intField = ByteField(1)
val stringField = ByteField("Sfield")
val doubleField = ByteField(2.0, Double::class) // explicit constructor
}
Upvotes: 1
Reputation: 3744
You need delegated properties for that.
Example:
import java.nio.ByteBuffer
import java.nio.charset.StandardCharsets
import kotlin.reflect.KProperty
interface Converter<T> {
fun fromBytes(raw: ByteArray): T
fun toBytes(typed: T): ByteArray
}
open class MyDelegate<T>(private val converter: Converter<T>) {
private lateinit var raw: ByteArray
operator fun getValue(thisRef: Any?, property: KProperty<*>) = converter.fromBytes(raw)
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
raw = converter.toBytes(value)
}
}
class StringDelegate : MyDelegate<String>(object : Converter<String> {
override fun fromBytes(raw: ByteArray) = String(raw, StandardCharsets.UTF_8)
override fun toBytes(typed: String) = typed.toByteArray(StandardCharsets.UTF_8)
})
data class Ints(val first: Int, val second: Int)
class IntsDelegate : MyDelegate<Ints>(object : Converter<Ints> {
override fun fromBytes(raw: ByteArray) = ByteBuffer.wrap(raw).let { Ints(it.int, it.int) }
override fun toBytes(typed: Ints) = ByteArray(8).apply {
with(ByteBuffer.wrap(this)) {
putInt(typed.first)
putInt(typed.second)
}
}
})
Usage:
class Test {
var string: String by StringDelegate()
var ints: Ints by IntsDelegate()
override fun toString(): String {
return "string: $string, ints: $ints"
}
}
fun main(args: Array<String>) {
val t = Test()
with(t) {
string = "first"
ints = Ints(1, 2)
}
println(t) // string: first, ints: Ints(first=1, second=2)
with(t) {
string += " + second"
ints = Ints(ints.first * 2, ints.second * 2)
}
println(t) // string: first + second, ints: Ints(first=2, second=4)
}
Please note that this all looks nice and elegant but might be rather expensive. E.g. consider ints = Ints(ints.first * 2, ints.second * 2)
- here we perform ByteArray -> Ints, access the first property; make ByteArray -> Ints once agains and access the second property and finally perform Ints -> ByteArray.
In case you access properties frequently and especially when they are large and complex that might be very costly.
Upvotes: 0