问题
对于Kotlin的默认的类对象来说,一个数据类访问成员的方法仍然是传统的点运算符来访问成员变量。
例如以下类定义:
class LegacyUser(){
var name: String? = null
var age: Int? = null
}
或者
//数据类
data class LegacyUser(var name: String, var age: Int)
我们访问时仍然只能按域成员访问方式去访问
val legacyUser = LegacyUser()
println(legacyUser.name)
println(legacyUser.age)
而无法类似Groovy或Js那样对一个类对象的属性使用[ ]
访问。
JavaScript的效果:
let pack = {
name: ''
}
pack.name = "Name1"
console.log(pack.name)
console.log(pack['name'])
pack['name'] = 'Name2'
console.log(pack.name)
console.log(pack['name'])
输出:
Name1
Name1
Name2
Name2
而在Groovy当中是如下代码和效果:
class MyPack {
public def name;
}
def pack = new MyPack()
pack.name = "Name1"
println(pack.name)
println(pack['name'])
pack.name = "Name2"
println(pack.name)
println(pack['name'])
输出:
Name1
Name1
Name2
Name2
以上语言自身的效果不能直接在kotlin中的类直接体现。
如果说我们在开发Android或者后端应用的时候我们有时候需要动态获取成员时,如何避免编写大量的反射代码来实现呢?
实现
Kotlin语言本身并没有设计这种语法,那我们是不是就没有办法了呢?答案是否定的。我们借助Kotlin自身的两个语法特性来实现这个效果。
- 委托——属性委托
- 操作符重载
借助这两个语法特性可以来实现我们的效果。
首先我们需要一个数据类,这个类需要一个在构造方法中成员map,另外编写数据字段,并且将这些字段委托给map。我们需要对变量进行读写功能,并且将读写的数据委托给map,需要在构造方法中使用MutableMap类型的映射。
当然了,MutableMap也可以使用HashMap来代替。
class MutableUser() {
val map: MutableMap<String, Any?> = mutableMapOf("name" to "", "age" to 0)
var name: String by map
var age: Int by map
}
现在这个类只有委托数据保存的位置,并没有增加[ ]
的访问性。
我们增加两个操作符重载方法来重载[ ]
的读写功能:
operator fun get(value: String): Any? {
return map[value]
}
operator fun set(key: String, value: String) {
map[key] = value
}
我们针对操作符进行了重载使得当前对象使用[ ]
时可以直接访问map当中的指定key的值。由此我们实现了上述特性的大部分功能。
在编写这个类的时候,要注意map在初始化为
val map: MutableMap<String, Any?> = mutableMapOf("name" to "", "age" to 0)
的时候,mutableMapOf
一定要传入与变量名相同的key,并且传入任意一定的数据,比如""
和0
,作为给key占位的数据。否则会在委托变量读写时发生错误。
我们编写如下类:
class MutableUser {
val map: MutableMap<String, Any?> = mutableMapOf("name" to "", "age" to 0)
var name: String by map
var age: Int by map
operator fun get(value: String): Any? {
return map[value]
}
operator fun set(key: String, value: String) {
map[key] = value
}
}
运行测试:
val mUser = MutableUser()
mUser.name = "Name1"
println(mUser.name)
println(mUser["name"])
mUser.name = "Name2"
println(mUser.name)
println(mUser["name"])
运行结果:
Name1
Name1
Name2
Name2
由此我们在kotlin中实现了通过委托和运算符重载实现了动态访问和修改类成员。
我们将上面的代码重新组织,以便于增加重用性和可读性。
open class Dynamic {
val dynamic: MutableMap<String, Any?> = mutableMapOf()
operator fun get(value: String): Any? {
return dynamic[value]
}
operator fun set(key: String, value: Any?) {
dynamic[key] = value
}
}
class MutableUser : Dynamic() {
var name: String by dynamic
}
如此将动态map作为父类来继承,数据字段作为派生类来定义,并且每个数据字段都委托给父类的map,我们就可以实现类似JavaScript和Groovy的动态字段访问的效果了。
但是一定要注意一点,在初始化数据类之后,切不可忘记为每个字段初始化一个值,否则直接访问则会报错。
测试代码:
fun main(args: Array<String>) {
val mUser = MutableUser()
mUser.name = "Name1"
println(mUser.name)
println(mUser["name"])
mUser["name"] = "Name2"
println(mUser.name)
println(mUser["name"])
}
结果:
Name1
Name1
Name2
Name2