为何Kotlin允许无backing field的扩展属性使用委托?
lazy委托? 核心原因很简单:扩展属性禁止直接赋值是因为没法给类实例添加专属的存储字段,但lazy委托的对象是静态全局存储的,和类实例无关,所以不违反扩展属性的限制。
先理清楚扩展属性的本质限制
扩展属性本质是「包装成属性形式的静态函数」,它不会修改原类的字节码结构——原类的实例里根本没有预留空间来存储扩展属性的backing field(后备字段)。这就是直接写val SomeClass.anExtensionProperty = "can't do this"会报错的原因:没有地方存这个值。
而用get()访问器的扩展属性(比如val SomeClass.anExtensionProperty get() = "xxx")之所以合法,是因为它每次调用都实时计算值,不需要存储任何状态,完全依赖静态逻辑返回结果。
再看lazy委托的特殊之处
当你给扩展属性用lazy委托时,这个lazy对象并不是存在SomeClass的实例里,而是作为全局静态变量存放在扩展函数所在的文件对应的类中。所有SomeClass的实例访问这个扩展属性时,都会共享同一个lazy对象。
对比类成员属性的lazy用法:成员属性的lazy委托是每个实例独有,存储在实例自己的backing field里;但扩展属性的lazy是全局唯一的,完全不依赖实例的存储空间,自然就符合扩展属性的规则。
底层编译后的逻辑(Java视角)
Kotlin编译后会把扩展属性转成静态方法,同时把lazy委托对象编译成静态常量,大致结构如下:
public final class YourExtensionFileKt { // 静态存储的lazy委托对象,全局唯一 private static final Lazy anExtentionsionProperty$delegate = LazyKt.lazy(() -> "you CAN do this, but why?"); // 扩展属性的getter静态方法,接收SomeClass实例作为参数(仅形式上传递,未使用其存储空间) public static final String getAnExtentionsionProperty(SomeClass $this$anExtentionsionProperty) { return (String) anExtentionsionProperty$delegate.getValue(); } }
可以看到,整个过程只是把SomeClass实例作为参数传入静态方法,完全不需要实例提供任何存储位置,所以不会触发扩展属性的限制。
注意点
这种扩展属性的lazy是全局共享的——所有SomeClass实例访问这个属性时,拿到的都是同一个值,且只会初始化一次。如果需要每个实例有独立的懒加载状态,这种写法就不适用了,你还是得用类的成员属性。
内容的提问来源于stack exchange,提问作者J-bob

