如何安全地解决这个空初始化器检查?
我正在写一个测试用例,我需要一些私有属性。 由于这些私人数据是从私人方法生成的,我决定在计算完成后使用反射来检索它们。 后来我想起了委托的财产,并决定写一个总代表。 这是我到目前为止的代码:
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可能是一个可以为空的类型,晚期的init机制将被打破,但是如果我初始化它为null
,它仍然抱怨,因为T可能是一个非null类型,安全被打破。
目前,我通过使用返回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 } }
一种方法是将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 } }