我正在Kotlin中寻找一种方法来传递泛型-但在编译时它们将具有固定的类型。
我的目标是创建一个基类Field<T>
,该基类将具有类型T的公共属性,该属性由ByteArray字段支持。
将使用全局转换器存储库在运行时与T进行ByteArray的转换。但是,我无法为Kotlin的类型擦除工作着迷,这是我在C#中没有遇到的事情(几乎相同的代码可以很好地工作)。
所以,我的总目标将是此类(注意,这主要是伪代码!):
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")
}
由于转换步骤始终相同,如果我可以动态地获取转换器并且不需要每次都创建继承类,那就太好了,即,我可以定义“正常工作”的字段:
var data1: Field<Int> = TODO("Init here")
var data2: Field<MyOjbect> = TODO("Init here")
当然,我想避免为每种类型实现getConverter()
。
有没有办法在Kotlin中使这项工作有效,或者我应该在这一部分坚持使用Java?
(附言:我显然不会为班级Field<T>
命名,但是这个名称似乎是描述其在这种情况下的作用的最通用的名称。)
答案 0 :(得分:1)
您可以使用reified type parameters,但是与为每种类型显式创建委托相比,它永远都不是编译时安全的,并且会带来运行时开销。
如果我理解您的问题和评论,那么您正在寻找这样的东西:
转换器界面:
interface Converter<T>{
fun convert(value: T) : ByteArray
fun convertBack(byteArray: ByteArray) : T
}
包含所有转换器的对象(单个):
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.
}
})
}
}
使用转换器的类,在mimicks构造函数之上的infline函数,但在调用时不需要显式的类参数:
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)
}
}
用法演示(当然,您也可以实现属性委托):
class Demo{
val intField = ByteField(1)
val stringField = ByteField("Sfield")
val doubleField = ByteField(2.0, Double::class) // explicit constructor
}
答案 1 :(得分:0)
示例:
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)
}
}
})
用法:
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)
}
请注意,这一切看起来都不错而优雅,但可能会相当昂贵。例如。考虑ints = Ints(ints.first * 2, ints.second * 2)
-在这里我们执行 ByteArray-> Ints ,访问 first 属性;再次使 ByteArray-> Ints 并访问 second 属性,最后执行 Ints-> ByteArray 。
如果您经常访问属性,尤其是当它们很大且很复杂时,可能会花费很大的钱。