属性及可见性
分类
类中的属性有两种:
-
可以存储值的属性
-
不存储属性值,每一次访问都需要通过 getter / setter 方法,因此每一次访问都需要计算。
interface Parent { val name: String val age: Int get() { println("getter") return 10 } } class Child:Parent{ override val name = "name" // 可以存储值。也可以通过 getter / setter 获取修改 set(value) { println("setter ${field} ${value}") field = value } }
getter 与 setter
-
在方法内部,使用特殊标识符 field 访问支持字段的值。
class Child { var i = 0 var name = "name" set(value) { println("setter ${field} ${value}") field = value // 为字段赋值 } get() { println("get ${field}") i++ field = "${i}" return "${i}" } } fun main(args: Array<String>) { val c = Child() println(c.name) c.name = "main--" println(c.name) }
其输出结果为:
-
可以修改 getter/setter 的可见性。
class Test { var name = "" private set(value) { // 将 setter 可见性设置为 private field = value } }
可见性
-
默认的修饰符是 public
-
kt 新增修饰符 internal:模板可见。一个模板指的是一起编译的 kt 文件。
-
protected 只能用于修饰类中的成员
-
可见性低的类不能用于可见性高的类、方法、属性。
-
与 java 一样,重写可以提升可见性,但不能降低可见性。
internal class User()
open class Person()
class Student:Person(){
internal var user:User = User()
internal fun test():User = User()
}
上述代码中,由于 User 定义为 internal,所以 Student 类的 user 属性、test() 方法都只能定义成比 internal 或比它低的可见性。
同理,如果将 Person 类的可见性定义 internal ,则 Student 不能继承,除非将 Student 也定义成 internal 或比它低的可见性。
修饰符 | 类成员 | 顶层声明 |
---|---|---|
public(默认) | 所有地方可见 | 所有地方可见 |
internal | 模块中可见 | 模块中可见 |
protected | 子类可见 | - (不能修饰) |
private | 类中可见 | 文件中可见 |
属性委托
-
使用关键字 by ,将属性的访问器委托给另一个实例。
-
属性委托只有在属性被访问时才进行初始化,与 lazy 函数类似。
格式
属性委托基本语法如下:
class Demo{
var p:Type by Delegate()
}
属性 p 将自己的 getter/setter 逻辑委托给了另一个对象,这里是 Delegate 的一个实例:通过对 by 后的表达式求值来获取这个实例。上述代码等价如下代码:
class Demo {
private val delegate = Delegate()
var p: Type
get() = delegate.getValue()
set(value) = delegate.setValue(value)
}
惰性初始化
lazy 函数会延迟加载,再结构 by 关键字可以实现委托属性。
在代码中,经常需要使用 惰性初始化:只有当需要该属性的时候才对属性进行初始化。
如下类,初始化时,并没有初始化 s,只有当访问 s 时才进行初始化,并且将初始化的值存储于 _s 中,这样下次访问时可以直接返回,这就是所谓的惰性初始化:
class Test {
private var _s: String? = null
val s: String
get() {
if (_s == null)
_s = "sss"
return _s!!
}
}
上述代码有点啰嗦,如果有多个惰性属性,这些模板代码需要写多次。而 属性委托可以让代码变得简单,可以封装用于存储值的支持属性以及确保该值只会被初始化一次。
委托类的构造
委托类有如下要求:
-
定义 getValue 与 setValue(如果是 val 变量,不需要)方法,且被声明为 operator
-
方法至少有两个参数,一个用于接收属性的实例;一个用于表示属性本身,其类型为 KProperty。可以通过 KProperty.name 获取属性名。
-
结合关键字 by ,kt 编译器自动完成 ‘惰性初始化‘ 中代码的功能
fun main(args: Array<String>) {
val d = Demo()
println(d.a) // 会输出 getValue ,然后再输出 1
d.a = 3 // 会输出 setValue
println(d.a) // 会输出 getValue ,然后再输出 3
}
class TestDelegate(var propValue: Int) {
operator fun getValue(demo: Demo, name: KProperty<*>): Int {
println("getValue")
return propValue
}
operator fun setValue(demo: Demo, name: KProperty<*>, newValue: Int) {
println("setValue")
propValue = newValue
}
}
class Demo {
var a: Int by TestDelegate(1)
}
从输出中可以发现:访问 a 属性时,所有的请求都转发到 TestDelegate 对应的方法中。这就是所谓的属性委托。