如何安全地解决初始化程序中的空检查?

时间:2017-01-05 08:11:52

标签: delegates kotlin

我正在编写一个测试用例,我需要一些私有属性。由于这些私有数据是从私有方法生成的,因此我决定在计算完成后使用反射来检索它们。后来我想起了委托的属性并决定写一个普通的委托。这是我到目前为止的代码:

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
    }
}

正如您所看到的,属性cachegetValue次调用初始化,如果设置了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
    }
}

1 个答案:

答案 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
    }
}