Kotlin学习系列(五)Kotlin属性(声明属性、延迟初始化属性、属性委托、惰性加载属性,可观察属性)

本系列内容均来自《Kotlin从小白到大牛》一书,感谢作者关东升老师。
属性是为了方便访问封装后的字段而设计的, 属性本身并不存储数据, 数据是存储在支持字段( backing field)中的。
Kotlin中属性可以在类中声明, 称为成员属性。 属性也可以在类之外, 类似于 顶层函数, 称为顶层属性, 事实上顶层属性就是全局变量。 本章介绍的属性主要是类 的成员属性。

1 回顾JavaBean

JavaBean 是一种Java语言的可重用组件技术, 它能够与JSP( Java Server Page)标签绑定, 很多Java框架也使用JavaBean。 JavaBean的字段( 成员变量) 往往被封装称为私有的, 为了能够在类的外部访问这些字段, 则需要通过getter和setter访问器访问。 动物( Animal) 类Java代码如下:

public class Animal {
// 动物年龄
    private int age = 1; 
// 动物性别
    private boolean sex = false; 
    public int getAge() { 
        return age;
    } 
    public void setAge(int age) { 
        this.age = age;
    } 
    public boolean isSex() { 
        return sex;
    } 
    public void setSex(boolean sex) { 
        this.sex = sex;
    }
}

如果使用Kotlin语言同样的类, 代码如下:

class Animal {
    // 动物年龄
    var age = 1
    // 动物性别
    var sex = false
}

可见Kotlin代码非常的简洁, 注意上述Animal类中的age和sex不是字段而属性, 一个属性对应一个字段, 以及 setter和getter访问器, 如果是只读属性则没有setter访问器。

2 声明属性

Kotlin中声明属性的语法格式如下:

var|val 属性名 [ : 数据类型] [= 属性初始化 ]
[getter访问器]
[setter访问器]

提示 属性本身并不真正的保存数据, 数据被保存到支持字段( backing field) 中, 支持字段一般是不可见的, 支持字段只能应用在属性访问器中, 通过系统定义好的field变量访问。
示例代码如下:

class Employee {
    var no: Int = 0 // 员工编号属性
    var job: String? = null // 工作属性 //①
    var firstName: String = "Tony" //②
    var lastName: String = "Guan" //③
    var fullName: String //全名 ④
        get() { //⑤
            return firstName + "." + lastName
        }
        set (value) { //⑥
            val name = value.split(".")// ⑦
            firstName = name[0]
            lastName = name[1]
        }
    var salary: Double = 0.0 // 薪资属性 ⑧
        set(value) {
            if (value >= 0.0) field = value// ⑨
        }
}

fun main(args: Array<String>) {
    val emp = Employee()
    println(emp.fullName)//Tony.Guan
    emp.fullName = "Tom.Guan"
    println(emp.fullName)//Tom.Guan
    emp.salary = -10.0 //不接收负值
    println(emp.salary)//0.0
    emp.salary = 10.0
    println(emp.salary)//10.0
}

注意 并不是所有的属性都有支持字段( backing field) 的, 例如上述代码中的fullName属性是通过另外属性计算而来, 它没有支持字段, 声明时不需要初始值。

3 延迟初始化属性

假设公司管理系统中两个类Employee( 员工) 和Department( 部门) , 它们的类图如下图所示, 它们有关联关系, Employee所在部门的属性dept与Department关联起来。 这种关联关系体现为: 一个员工必然隶属于一个部门, 一个员工实例对应于一个部门实例。


image.png

看下列代码:

// 员工类
class Employee {
    ...
    var dept = Department() // 所在部门属性 ①
} 
// 部门类
class Department {
    var no: Int = 0 // 部门编号属性
    var name: String = "" // 部门名称属性
} 
//代码文件: chapter11/src/com/a51work6/section4/s3/ch11.4.3.kt
fun main(args: Array<String>) {
    val emp = Employee()
    ...
    println(emp.dept)
}

在创建Employee对象时, 需要同时需要实例化Employee的所有属性, 也包括实例化dept( 部门) 属性, 代码第①行声明dept属性的同时进行了初始化, 创建Department对象。 如果是一个新入职的员工, 有时不关心员工在哪个部门, 只关心他的no( 编号) 和name( 姓名) 。 但上述代码虽然不使用dept对象, 但是仍然会实例化它, 这样会占用内存
Kotlin可以对属性设置为延迟初始化的, 修改代码如下:

// 员工类
class Employee {
    ...
    lateinit var dept: Department // 所在部门属性 ①
} 
// 部门类
class Department {
    var no: Int = 0 // 部门编号属性
    var name: String = "" // 部门名称属性
} 
fun main(args: Array<String>) {
    val emp = Employee()
    ...
    emp.dept = Department()
    println(emp.dept)
}

在声明dept属性前面添加了关键字lateinit, 这样dept属性就是延时初始化。 顾名思义, 延时初始化属性就是不必在类实例化时初始化它, 可以根据需要在程序运行期初始化。 而没有lateinit声明的非可空类型属性必须在类实例化时初始化。
提示 延时初始化属性要求: 不能是可空类型; 只能使用为var声明; lateinit关键字应该放在var之前。

4 委托属性

Kotlin提供一种委托属性, 使用by关键字声明, 示例代码如下:

class User {
    var name: String by Delegate()
}

class Delegate {
    operator fun getValue(thisRef: Any, property: KProperty<*>): String {
        return property.name
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println(value)
    }

}

fun main(args: Array<String>) {
    val user = User()
    user.name = "Tom"
    println(user.name)
}

上述代码声明委托属性,by是委托运算符, 它后面的Delegate()就是属性name的委托对象, 通过by运算符属性name的setter访问器被委托给Delegate对象的setValue函数, 属性name的getter访问器被委托给Delegate对象的getValue函数。
Delegate对象不必实现任何接口, 只需要实现getValue和setValue函数即可, 注意这两个函数前面都有operator关键字修饰operator所修饰的函数是运算符重载函数, 本例中说明了getValue和setValue函数重载by运算符。给name属性赋值, 这会调用委托对象的setValue函数, 读取name数组值, 这会调用委托对象的getValue函数。

5 惰性加载属性

惰性加载属性与延迟初始化属性类似, 只有第一次访问该属性时才进行初始化。 不同的是惰性加载属性使用的lazy函数声明委托属性, 而延迟初始化属性lateinit关键字修饰属性还有惰性加载属性必须是val的, 而延迟初始化属性必须是var的
示例代码如下:

open class Employee {
    var no: Int = 0 // 员工编号属性
    var firstName: String = "Tony"
    var lastName: String = "Guan"
    val fullName: String by lazy { 
        firstName + "." + lastName
    } 
    lateinit var dept: Department 
} 
// 部门类
class Department {
    var no: Int = 0 // 部门编号属性
    var name: String = "" // 部门名称属性
} 
fun main(args: Array<String>) {
    val emp = Employee()
    println(emp.fullName)//Tony.Guan
    val dept = Department()
    dept.no = 20
    emp.dept = dept
    println(emp.dept)
}

注意lazy不是关键字, 而是函数。 lazy函数后面跟着的是尾随Lambda表达式。 惰性加载属性使用val声明。

6 可观察属性

另一个使用委托属性示例是可观察属性, 委托对象监听属性的变化, 当属性变化时委托对象会被触发。
实例代码如下:

// 部门类
class Department {
    var no: Int = 0 // 部门编号属性
    var name: String by Delegates.observable("<无>") { p, oldValue, newValue -> 
        println("$oldValue -> $newValue")
    }
} 
fun main(args: Array<String>) {
    val dept = Department()
    dept.no = 20
    dept.name = "技术部" //输出<无> -> 技术部 ②
    dept.name = "市场部" //输出技术部 -> 市场部 ③
}

声明name委托属性, by关键字后面Delegates.observable()函数有两个参数第一个参数是委托属性的初始化值第二个参数是属性变化事件的响应器,响应器是函数类型, 具体调用时可使用Lambda表达式作为实际参数。 在用Lambda表达式中有三个参数, 其中p是属性, oldValue是属性的旧值, newValue是属性的新值。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352

推荐阅读更多精彩内容