我正在编写一个测试用例,我需要一些私有属性。由于这些私有数据是从私有方法生成的,因此我决定在计算完成后使用反射来检索它们。后来我想起了委托的属性并决定写一个普通的委托。这是我到目前为止的代码:
fun <T> reflect(instance: Any, initOnce: Boolean = true) = ReflectBackedProperty<T>(initOnce, instance)
class ReflectBackedProperty<T>(val initOnce: Boolean, val instance: Any): ReadOnlyProperty<Any, T> {
var initialized = false
lateinit var cache: T // <--- (a)
override opertaor fun getValue(thisRef: Any, property: KProperty<*>): Any? {
@Suppress("UNCHECKED_CAST")
if (!initialized || !initOnce) {
cache = instance.javaClass.getDeclaredField(property.name).get(instance) as T
initialized = true
}
return cache
}
}
正如您所看到的,属性cache
由getValue
次调用初始化,如果设置了initOnce
,后续调用将使用该缓存而不是保持调用昂贵的反射。
非常不幸的是,在(a)编译器抱怨因为T可能是可空类型而后期初始化机制将被破坏,但是如果我用null
初始化它并且仍然抱怨因为T可能是非空类型和空安全性被打破。
目前,我通过使用返回null的Java函数的返回值初始化它来使其工作。我检查了生成的字节码,发现kotlin编译器没有对它进行空检查,所以它现在可以工作,但我担心未来的kotlin版本会有这样的检查并破坏这个技巧。我该如何克服这个?
现在我正在使用此代码,以下代码将发布到公共领域。如果您愿意,请提及此页面,或者什么都不做。
KTHacks.java
public final class KTHacks {
private KTHacks() {
throw new UnsupportedOperationException();
}
/**
* Forge a null into a platform type and take advantage of relaxed null-checks.
* @param <T>
* @return
*/
public static <T> T NULL() {
return null;
}
}
ReflectBackedProperty.kt
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
fun <T> reflect(instance: Any, initOnce: Boolean = true) = ReflectBackedProperty<T>(initOnce, instance)
class ReflectBackedProperty<T>(val initOnce: Boolean, val instance: Any): ReadOnlyProperty<Any, T> {
var initialized = false
var cache: T = KTHacks.NULL()
override operator fun getValue(thisRef: Any, property: KProperty<*>): T {
@Suppress("UNCHECKED_CAST")
if (!initialized || !initOnce) {
cache = instance.javaClass.getDeclaredField(property.name).get(instance) as T
initialized = true
}
return cache
}
}
答案 0 :(得分:3)
一种方法是将T
限制为仅具有上限的非可空类型:
class ReflectBackedProperty<T: Any> : ReadOnlyProperty<Any, T> {}
另一种方法是不打扰lateinit
:
class ReflectBackedProperty<T>(val initOnce: Boolean, val instance: Any): ReadOnlyProperty<Any, T> {
var initialized = false
var cache: T? = null
override operator fun getValue(thisRef: Any, property: KProperty<*>): T {
if (!initialized || !initOnce) {
cache = instance.javaClass.getDeclaredField(property.name).get(instance) as T
initialized = true
}
return cache as T
}
}