本章内容包括:
- 运算符重载
- 约定:支持各种运算的特殊命名函数
- 委托属性
7.1 运算符
代码语言:javascript复制 /**-------------------- 7.1.1 重载二元算术运算 ----------------------*/
// 代码清单7.1 定义一个plus运算符
data class Point(val x: Int, val y: Int) {
// 定义一个名为 plus 的方法
operator fun plus(other: Point): Point {
return Point(x other.x, y other.y)
}
}
println(Point(10, 10).plus(Point(12, 13)).x)// 22
val point = Point(10, 10)
val point2 = Point(12, 13)
// 通过使用 号来调用plus方法
println(point point2)// Point(x=22, y=23)
/*
* 在使用了operator修饰符声明了plus函数后,你就可以直接使用_好来求和了。
* 除了这个运算符声明为一个 成员函数 外,也可以把它定义为一个扩展函数
*/
// 代码清单7.2 把运算符定义为扩展函数
operator fun Point.plus(other: Point): Point {
return Point(x other.x, y other.y)
}
/*
* 可重载的二元算术运算符
* 表达式 函数名
* a * b times
* a / b div
* a % b mod
* a b plus
* a - b minus
*/
// 代码清单7.3 定义一个运算数类型不同的运算符
operator fun Point.times(scale: Double): Point {
return Point((x * scale).toInt(), (y * scale).toInt())
}
val point3 = Point(10, 20)
println(point3 * 2.0)// Point(x=20,y=40)
// 注意不能使用 2.0 * point3,如果需要使用需要另定义
operator fun Double.times(point: Point): Point {
return Point((this * point.x).toInt(), (this * point.y).toInt())
}
println(3.0 * point3)// Point(x=30,y=60)
// 代码清单7.4 定义一个返回结果不同的运算符
operator fun Char.times(count: Int): String {
return toString().repeat(count)
}
println('a' * 3)// aaa
/*
* 没有用于位运算的特殊运算符
* 以下,用于执行位运算的完整函数列表
* shl -- 带符号左移
* shr -- 带符号右移
* ushr -- 无符号右移
* and -- 按位与
* or -- 按位或
* xor -- 按位异或
* inv -- 按位取反
*/
// 使用方法:
println(0x0F and 0x0F)// 0
println(0x0F or 0x0F)// 255
println(0x1 shl 4)// 16
/**-------------------- 7.1.2 重载复合赋值运算符 ----------------------*/
// 对可变变量var有效,例子:
var point1 = Point(1, 2)
point1 = Point(1, 1)
println(point1)// Point(x=2,y=3)
// 将元素添加到可变集合,例子:
val numbers = ArrayList<Int>()
numbers = 42
println(numbers[0])// 42
/*
* 如果你定义了一个返回值 Unit,名为plusAssign函数,Kotlin将会在用到 =运算符的地方调用它。
* 其他如 minusAssign、timeAssign
*/
operator fun <T> MutableCollection<T>.plusAssign(element: T) {
this.add(element)
}
/*
* a = b
* a = a.plus(b)
* a.plusAssign(b)
* 运算符 = 可以被转换为plus或者plusAssign函数的调用
* 和 - 运算符总是返回一个新的集合。
* =和-=运算符用于可变集合时,始终就地修改它们,用于只读集合时,会返回一个修改过的副本。
*/
val list = arrayListOf(1, 2)
list = 3
val newList = list listOf(4, 5)
println(list)// 就地修改 [1,2,3]
println(newList)// 新的集合 [1,2,3,4,5]
/**-------------------- 7.1.3 重载一元运算符 ----------------------*/
// 代码清单7.5 定义一个一元运算符
operator fun Point.unaryMinus(): Point {
// 一元运算符无参数
return Point(-x, -y)
}
val point4 = Point(10, 20)
println(-point4)// Point(x=-10,y=-20)
/*
* a -- a.unaryPlus()
* 一元运算符 被转换为unaryPlus函数的调用
* 可重载的一元算法的运算符
* 表达式 函数名
* a unaryPlus
* -a unaryMinus
* !a not
* a,a inc
* --a,a-- dec
*/
// 代码清单7.6 定义一个自增运算符
operator fun BigDecimal.inc() = this BigDecimal.ONE
var db = BigDecimal.ZERO
println(db )// 0 在执行后添加
println( db)// 2 在执行前添加
7.2 重载比较运算符
代码语言:javascript复制 /**-------------------- 7.2.1 等号运算符 equals ----------------------*/
/*
* == != 可以用于可空运算数
* a == b -> a? equals(b) ?: (b==null)
*/
// 代码清单7.7 实现equals函数
class Point(val x: Int, val y: Int) {
// 重写在Any中定义的方法
override fun equals(other: Any?): Boolean {
// 优化:检查参数是否与this是统一对象
if (other === this) return true
// 检查参数类型
if (other !is Point) return false
// 智能转换为Point来访问x、y属性
return other.x == x && other.y == y
}
}
println(Point(10, 20) == Point(10, 20))// true
println(Point(10, 20) != Point(5, 5))// true
println(null == Point(10, 20))// false
// 恒等运算符(===):来检查参数与调用equals的对象是否相同。
// Any中的基本方法已经将equals方法标记为 operator 了
/**-------------------- 7.2.2 排序运算符 compareTo ----------------------*/
/*
* a >= b -> a.compareTo(b) >= 0
* 两个对象的比较被转换为compareTo的函数调用,然后结果与零比较
*/
// 代码清单7.8 实现 compareTo 方法
class Person(val firstName: String, val lastName: String) : Comparable<Person> {
override fun compareTo(other: Person): Int {
// 按顺序调用给定的方法,并比较它们的值
return compareValuesBy(this, other, Person::lastName, Person::firstName)
}
}
val person = Person("Alice", "Smith")
val person2 = Person("Bob", "Johnson")
println(person < person2)// false
/*
* compareValuesBy 这个函数接收用来计算比较值的一系列回调,按顺序依次调用回调方法,两两一组分别做比较,
* 并返回结果。如果值不同,则返回比较结果;如果它们相同,则继续调用下一个;如果没有更多的回调来调用,则返回0。
* */
println("abc" < "bac")// true
7.3 集合与区间的约定
代码语言:javascript复制 /**-------------------- 7.3.1 通过下标来访问元素 get 和 set ----------------------*/
// kotlin中可以使用类似java中数组中的方式来访问map中的元素---使用方括号
// val value = map[key]
// 也可以用同样的运算符来改变一个可变map的元素
// mutableMap[key] = newValue
// 使用方括号来引用点的坐标:p[0]访问x坐标,p[1]访问y坐标。
// 代码清单7.9 实现 get 约定
// 定义一个名为 get 的运算符函数
operator fun Point.get(index: Int): Int {
// 根据给出的index返回对应的坐标
return when (index) {
0 -> x
1 -> y
else -> throw IndexOutOfBoundsException("Invalid coordinate $index")
}
}
val point = Point(1, 2)
println(point[1])// 2
/*
* x[a,b] -> x.get(a,b)
*
* get的参数可以是任何类型,而不只是Int,还可以是多个参数。例如 matrix[row,rol]
*/
// 代码清单7.10 实现 set 的约定方法
data class MutablePoint(var x: Int, var y: Int)
// 定义一个名为 set 的运算符函数
operator fun MutablePoint.set(index: Int, value: Int) {
when (index) {
// 根据给出的index参数修改对应的坐标
0 -> x = value
1 -> y = value
else -> throw java.lang.IndexOutOfBoundsException("Invalid coordinate $index")
}
}
val mutablePoint = MutablePoint(10, 20)
mutablePoint[1] = 42
println(mutablePoint)// MutablePoint(x=10,y=42)
// set 最后一个参数用来接收赋值语句中等号右边的值。
// x(a,b) = c -> x.set(a,b,c)
/**-------------------- 7.3.2 in 的约定 ----------------------*/
// 代码清单7.11 实现in的约定
data class Rectangle(val upperLeft: Point, val lowerRight: Point)
operator fun Rectangle.contains(p: Point): Boolean {
// 构建一个区间,检查坐标x是否属于这个区间,使用 until 函数来构建一个开区间
return p.x in upperLeft.x until lowerRight.x &&
p.y in upperLeft.y until lowerRight.y
}
/*
* in 右边的对象将会调用 contains 函数,in 左边的对象将会作为函数入参。
* a in c -> c.contains(a)
* in 10..20 [10,20] 用 10..20构建一个普通的闭区间。
* in 10 until 20 [10,20) 开区间是不包括最后一个点的区间。
*/
/**-------------------- 7.3.3 rangeTo 的约定 ----------------------*/
/*
* start..end -> start.rangeTo(end)
* .. 运算符将被转换为 rangeTo函数的调用
*/
// 这个函数返回一个区间,可以用来检测其他一些元素是否属性它。
// operator fun <T : Comparable<T>> T.rangeTo(that: T): ClosedRange<T>
// 代码清单7.12 处理日期的区间
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val now = LocalDate.now()
// 创建一个从今天开始的10天的区间
val closedRange = now..now.plusDays(10)
// 检测一个特定的日期是否属于这个区间
println(now.plusWeeks(1) in closedRange) // true
// rangeTo 运算符的优先级低于算术运算符,但是最好把参数括起来
val n = 9
println(0..(n 1))// 0..10
(0..9).forEach {
// 把区间括起来,来调用它的方法
println(it)// 0123456789
}
/**-------------------- 7.3.4 在 for 循环中使用 iterator 的约定 ----------------------*/
// in 在for循环中使用被执行迭代,转换成 list.iterator()
// 这个库函数让迭代字符串成为可能
// operator fun CharSequence.iterator():CharIterator
for (c in "abc") {
println(c)
}
// 代码清单7.13 实现日期区间的迭代器
operator fun ClosedRange<LocalDate>.iterator(): Iterator<LocalDate> =
// 这个对象实现了遍历LocalDate元素的Iterator
object : Iterator<LocalDate> {
var current = start
// 注意,这里日期用到了compareTo约定
override fun hasNext() = current <= endInclusive
// 在修改前返回当前日期作为结果
override fun next() = current.apply {
// 把当前日期增加一天
current = plusDays(1)
}
}
val ofYearDay = LocalDate.ofYearDay(2017, 1)
// val dayOff = ofYearDay.minusDays(1)..newYear()
// 对应的 iterator函数实现后,遍历daysOff
// for (dayOff in daysOff) {
// println(dayOff)
// }
}
7.4 解构声明和组件函数
代码语言:javascript复制 class Point(val x: Int, val y: Int) {
operator fun component1() = x
operator fun component2() = y
}
// 解构声明:允许你展开单个复合值,并使用它来初始化多个单独变量
val point = Point(10, 20)
val (x, y) = point
println(x)// 10
println(y)// 20
/*
* 一个解构声明看起来像一个普通的变量声明,但它在括号中有多个变量。约定原理。
* val (x,y) = point :
* val a = p.component1()
* val b = p.component2()
*
* 主要场景之一:从一个函数返回多个值。
*/
// 代码清单7.14 使用解构声明来返回多个值
// 声明一个数据类来持有值
data class NameComponents(val name: String, val extension: String)
fun splitFileName(fullName: String): NameComponents {
val result = fullName.split(".", limit = 2)
// 返回也该数据类型的示例
return NameComponents(result[0], result[1])
}
// 使用解构声明来展开这个类
val (name, ext) = splitFileName("example.kt")
println(name)// example
println(ext)// ext
// 代码清单7.15 使用解构声明来处理集合
fun splitFilename(fullName: String): NameComponents {
val (name, extension) = fullName.split(".", limit = 2)
return NameComponents(name, extension)
}
// 标准库只允许使用此语法来访问一个对象的前五个元素,让一个函数能返回多个值有更简单的方法,使用标准库中的Pair和Triple类。
/**-------------------- 7.4.1 解构声明和循环 ----------------------*/
// 代码清单7.16 用解构声明来遍历 map
// 使用这个语法来打印给定map中的所有条目
fun printEntries(map: Map<String, String>) {
for ((key, value) in map) {
// 在 in 循环中用解构声明
println("$key -> $value")
}
// 等同于这个
for (entry in map.entries) {
val key = entry.component1()
val value = entry.component2()
println("$key -> $value")
}
}
val mapOf = mapOf("Oracle" to "Java", "JetBrains" to "Kotlin")
printEntries(mapOf)
// 使用了两个Kotlin约定:一个是迭代一个对象 一个是用于解构声明
7.5 重用属性访问的逻辑:委托属性
代码语言:javascript复制 /*
* Kotlin中最独特和最强大的功能:委托属性
* 委托是一种设计模式,操作的对象不用自己执行,而是把工作微委托给另一个辅助的对象。我们把辅助对象称为委托。
*/
/**-------------------- 7.5.1 委托属性的基本操作 ----------------------*/
// 基础语法:
// class Foo {
// 关键字 by 把属性关联上委托对象
// var p: Type by Delegate()
// }
class Foo {
// 编译器会自动生成一个辅助属性
// private val delegate = Delegate()
// p 的访问都会调用对应的 delegate 的getValue和setValue方法
// var p: Type
// set(value: Type) = delegate.setValue(..., value)
// get() = delegate.getValue(...)
}
class Delegate {
// getValue 包含了实现setter的逻辑
// fun getValue(...): Type {
// ...
// }
// setValue 包含了实现setter的逻辑
// fun setValue(x: Int, any: Type) {
// ...
// }
//
}
// val foo = Foo()
// 通过调用 delegate.getValue(...)来实现属性的修改
// val oldValue = foo.p
// 通过调用 delegate.setValue(...,newValue)来实现属性的修改
// foo.p = newValue
/**-------------------- 7.5.2 使用委托属性:惰性初始化和 by lazy() ----------------------*/
// 惰性初始化时一种常见的模式,知道在第一次访问该属性的时候,才根据需要创建对象的一部分。
class Email
// 使用额外的 _emails 属性来实现惰性加载,在没有加载之前为null,然后加载为邮件列表
// 代码清单7.17 使用支持属性来实现惰性初始化
class Person(val name: String) {
// _emails 属性用来保存数据,关联委托
private var _emails: List<Email>? = null
val emails: List<Email>
get() {
if (_emails == null) {
// 访问时加载邮件
_emails = loadEmails(this)
}
// 如果已经加载,就直接返回
return _emails!!
}
fun loadEmails(person: Person): List<Email> {
println("Load email for ${person.name}")
return listOf()
}
}
val p = Person("jingbin")
p.emails// 第一次访问会加载邮件: Load email for jingbin
p.emails
/*
* 这里使用了所谓的 支持属性技术。有一个属性 _emails 用来存储这个属性,而另一个email用来提供对属性的读取访问。
* 这样代码有点啰嗦,而且线程不安全,kotlin有更好的方案,使用标准库函数 lazy 返回的委托。
*/
// 代码清单7.18 用委托属性来实现惰性初始化
class Person2(val name: String) {
// lazy 的参数是一个Lambda,可以调用它来初始化这个值,且默认是线程安全的。
val emails by lazy { loadEmail(this) }
private fun loadEmail(person: Person2): List<Email> {
println("Load email2 for ${person.name}")
return listOf()
}
}
/**-------------------- 7.5.3 实现委托属性 ----------------------*/
// 代码清单7.19 使用 PropertyChangeSupport 的工具类
open class PropertyChangeAware {
protected val changeSupport = PropertyChangeSupport(this)
fun addPropertyChangeListener(listener: PropertyChangeListener) {
changeSupport.addPropertyChangeListener(listener)
}
fun removePropertyChangeListener(listener: PropertyChangeListener) {
changeSupport.removePropertyChangeListener(listener)
}
}
// 代码清单7.20 手工实现属性修改的通知
class Person3(val name: String, age: Int, salary: Int) : PropertyChangeAware() {
var age: Int = age
set(newValue) {
// field 标识符允许你访问属性背后的支持字段
val oldValue = field
field = newValue
changeSupport.firePropertyChange("age", oldValue, newValue)
}
var salary: Int = salary
set(newValue) {
val oldValue = field
field = newValue
changeSupport.firePropertyChange("salary", oldValue, newValue)
}
}
val p3 = Person3("jingbin", 30, 40000)
// 关联监听器,用于监听属性修改
p3.addPropertyChangeListener(PropertyChangeListener { it ->
println("Property ${it.propertyName} changed" "from ${it.oldValue} to ${it.newValue}")
})
p3.age = 35 // Property age changed form 30 to 35
p3.salary = 60000 // Property salary changed form 40000 to 60000
// 代码清单7.21 提过辅助类来实现属性变化的通知
class ObservableProperty(val propName: String, var propValue: Int, val changeSupport: PropertyChangeSupport) {
fun getValue(): Int = propValue
fun setValue(newValue: Int) {
val oldValue = propValue
propValue = newValue
changeSupport.firePropertyChange(propName, oldValue, newValue)
}
}
class Person4(val name: String, age: Int, salary: Int) : PropertyChangeAware() {
val _age = ObservableProperty("age", age, changeSupport)
var age: Int
get() = _age.getValue()
set(value) {
_age.setValue(value)
}
val _salary = ObservableProperty("salary", salary, changeSupport)
var salary: Int
get() = _salary.getValue()
set(value) {
_salary.setValue(value)
}
}
/*
* 你创建了一个保存属性值的类,并在修改属性时自动触发更改通知。你删除了重复的逻辑代码,但是需要相当多的样板代码来为每个属性创建
* ObservableProperty 实例,并把getter和setter委托给它。Kotlin的委托属性可以让你摆脱这些样板代码。
*/
// 代码清单7.22 ObservableProperty 作为属性委托
// class ObservableProperty2(var propValue: Int, val changeSupport: PropertyChangeSupport) {
// operator fun getValue(p: Person5, prop: KProperty<*>): Int = propValue
// operator fun setValue(p: Person5, prop: KProperty<*>, newValue: Int) {
// val oldValue = propValue
// propValue = newValue
// changeSupport.firePropertyChange(prop.name, oldValue, newValue)
// }
// }
// 代码清单7.23 使用委托属性来绑定更改通知
// class Person5(val name: String, age: Int, salary: Int) : PropertyChangeAware() {
// var age: Int by ObservableProperty2(age, changeSupport)
// var salary: Int by ObservableProperty2(salary, changeSupport)
// }
// 右边的对象被称为委托,Kotlin会自动将委托存储在隐藏的属性中,并在访问或修改属性时调用委托的geyValue,和setValue
// 代码清单7.24 使用Delegates.observable来实现属性修改的通知
class Person5(val name: String, age: Int, salary: Int) : PropertyChangeAware() {
private val observer = { prop: KProperty<*>, oldValue: Int, newValue: Int ->
changeSupport.firePropertyChange(prop.name, oldValue, newValue)
}
var age: Int by Delegates.observable(age, observer)
var salary: Int by Delegates.observable(salary, observer)
}
/*
* by 右边的表达式不一定是新创建的实例,也可以是函数调用、另一个属性、或任何其他表达式,
* 只要这个表达式的值,是能够被编译器用正确的参数类型来调用getValue和setValue的对象。
*/
/**-------------------- 7.5.4 委托属性的变换规则 ----------------------*/
// class C{
// var prop: Type by MyDelegate()
// }
// val c = C()
/*
* MyDelegate 实例会被保存到一个隐藏的属性中,它被称为<delegate>。编译器也会将用一个KProperty类型的对象来代表这个属性,它被称为<property>。
* 编译器生成的代码如下:
*/
// class C {
// private val <delegate> = MyDelegate()
// var prop: Type
// get() = <delegate>.getValue(this, <property>)
// set(value:Type) = <delegate>.setValue(this,<property>,value)
// }
/*
* val x = c.prop -> val x = <delegate>.getValue(c, <property>)
* c.prop = x -> <delegate>.setValue(c, <property>, x)
*/
/**-------------------- 7.5.5 在 map 中保存属性值 ----------------------*/
// 代码清单7.25 定义一个属性,把值存在map
class Person6 {
private val _attributes = hashMapOf<String, String>()
fun setAttribute(attrName: String, value: String) {
_attributes[attrName] = value
}
// 从map手动检索属性
val name: String
get() = _attributes["name"]!!
}
val p6 = Person6()
val data = mapOf("name" to "jingbin", "company" to "ali")
for ((attrName, value) in data) {
p6.setAttribute(attrName, value)
}
println(p.name) // jingbin
// 代码清单7.26 使用委托属性把值存到map中
class Person7 {
private val _attributes = hashMapOf<String, String>()
fun setAttribute(attrName: String, value: String) {
_attributes[attrName] = value
}
// 把 map 作为委托属性
val name: String by _attributes
}
/**-------------------- 7.5.6 框架中的委托属性 ----------------------*/
// 代码清单7.27 使用委托属性来访问数据库列
// user 对应数据库中的表
// object Users : IdTable() {
// name 和 age 对应数据库表的列
// val name: varchar("name", length = 50).index()
// val age = ingeter("age")
// }
// 每一个User示例对应表中的一个实体
// class User(id: EntityID) : Entity(id) {
// name 的值是数据库中对应那个用户的值
// var name: String by Users.name
// var age: Int by Users.age
// }
总结
- Kotlin 允许使用对应名称的函数来重载一些标准的数学运算,但是不能定义自己的运算符。
- 比较运算符映射为 equals和 compareTo 方法的调用。
- 通过定义名为 get set contains 的函数,就可以让你自己的类与Kotlin 的集合一样,使用[]和 in 运算符。
- 可以通过约定来创建区间,以及迭代集合和数组。
- 解构声明可以展开单个对象用来初始化多个变量,这可以方便地用来从函数返回多个值。它们可以自动处理数据类,可以通过给自己的类定义名为 componentN 的函数来支持。
- 委托属性可以用来重用逻辑,这些逻辑控制如何存储、初始化、访问和修改属性值,这是用来构建框架的一个强大的工具。
- lazy 标准库函数提供了一种实现惰性初始化属性的简单方法。
- Delegates.observable 函数可以用来添加属性更改的观察者。委托属性可以使用任意 map 来作为属性委托,来灵活来处理具有可变属性集的对象。