目录

Kotlin 扩展属性无法使用

Kotlin 扩展属性的小坑和解决方案

在 Java 中, 我们常常要写烦人的 getter/setter 方法, 因为我们保不准以后什么时候需要在修改或者获取此值的时候做些手脚.

Kotlin 帮我们生成了这个东西, 所有属性默认是 private, 而我们加在属性上的可见性修饰符, 事实上加在了它们的 setter/getter 方法上:

比如下面的 kotlin 代码:

1
2
3
4
5
6
7
class Awa {
    var b: String = ""
        get() = field
        set(value) {
            field = value
        }
}

与下面这段 java 代码等价:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Awa {
    public String getB() {
        return b;
    }

    public void setB(String b) {
        this.b = b;
    }

    private String b = "";
}

而 Kotlin 里面的 field, 实际上代表的是 Java 代码中的"幕后属性": b

当然, 在 Kotlin 里我们可以使用 @JvmField 来使得可见性修饰符作用到真实的属性(幕后属性)

扩展属性

Kotlin 有个很方便的功能叫做扩展属性, 比如我们自定义 View 中经常用到:

1
2
val Int.dp get() = //...
val Int.sp get() = //...

事实上如果我们直到扩展函数的原理, 以及 Java 如何调用 Kotlin 的扩展函数的话, 我们就清楚, Kotlin 的扩展函数实际上是将被扩展的类的引用放到了函数的第一个参数, 再配合 IDE 的提示, 来达到这样的效果的.

扩展属性, 实质上只是扩展了这个属性的 get 和 set 方法, 而对于幕后字段, Kotlin 其实是并不会去修改原来的类的, 因此我们是获取不到的, 如下:

https://cdn.jsdelivr.net/gh/zsqw123/cdn@master/picCDN/20211004165736.png

我就想干涉这个流程

这个流程也不是不能干涉, 直接加一个容器, 重拳出击:

https://cdn.jsdelivr.net/gh/zsqw123/cdn@master/picCDN/20211004170125.png

事实上是创建了一个映射关系, 当然, 这个 map 应该使用 private 让其对外部不可见, 以及在 Android 里, 如果数据规模不大, 也可以使用更轻量 ArrayMap 来减少内存的占用.

但是这样还是看着不舒服啊, Map 导致的内存占用让我质疑这个操作的合理性, 我每次用到都需要去手动写这种垃圾代码, 这太烦了! 于是我直接想到 Kotlin 属性代理, 内部我自己去添加一个"幕后属性"不就行了? 去实现类似于下面这种使用方式:

1
2
3
4
5
6
7
8
var Awa.b by exVar("defVal")
var Awa.c by exVar(123, getter = { it + 1 })
var Awa.d by exVar(
    defVal = 999,
    getter = { it + 100 },
    setter = { it + 10 },
)
var Awa.e: String? by exVar(null)

具体实现:


gist: kotlin 扩展属性 set 方法无法使用 feild 处理办法 (github.com)