一.重载算数运算符

​ Kotlin中最简单明了的使用约定的例子就是算数运算符。在Java中算数运算符只可以使用在基本数据类型上,+号可以使用在String上。当我们想在BigInteger类上使用+,或者想使用+=添加元素到一个集合中时Java就做不到了。但是Kotlin中就可以做到。

1.重载二元算数运算符

​ 首先从+开始,实现将两个点的坐标值加起来,使用operator修饰符定义一个操作符函数

data class Point(val x: Int, val y: Int) {

operator fun plus(other: Point): Point {

return Point(x + other.x, y + other.y)

}

}

定义Point对象,此时可以使用+号

val point = Point(10, 20)

val point2 = Point(20, 30)

println(point + point2)

>>Point(x=30, y=50)

​ 也可以把操作符函数定义成扩展函数,而使用扩展函数语法也会是一种通用的定义操作符扩展函数的模板

operator fun Point.plus(other: Point): Point {

return Point(x + other.x, y + other.y)

}

​ Kotlin不允许你自己定义操作符,下面是可以重载的操作符及函数名。为自己写的Kotlin类定义的算数运算符其优先级和数字运算符是相同的。

 当我们定义操作符时,他接收的两个操作数可以是不同类型的,操作符函数的返回值也可以是不同类型的。但是需要注意的是Kotlin操作符不支持左右两个操作数交换顺序

2.重载复合赋值操作符

​ 正常情况下,当定义了一个操作符比如plus时,Kotlin会同时支持+和+=操作符。+=和-=叫做复合赋值操作符

//复合赋值运算符

var point3 = Point(1, 2)

point3 += Point(2, 4)

println(point3)

>> Point(x=3, y=6)

+=运算符重载会定义一个叫plusAssign的函数,他没有返回值,因为调用者会执行自增逻辑:

public inline operator fun MutableCollection.plusAssign(element: T) {

this.add(element)

}

minusAssign,timesAssign也是类似的,Kotlin标准库为可变集合定义了plusAssign这一类的函数。

​所以当你在代码中使用+=时,理论上plus和plusAssign都会被调用。我们应该避免同时为+=添加plus和plusAssign操作符。如果调用者是不可变的(用val修饰),你应该只提供plus操作符返回一个全新的变量,如果调用者是可变的(var修饰),你应该只需要提供plusAssign操作符直接去修改调用者的值。另外在集合操作中,+和-不管是否是可变集合都会返回一个新的集合;+=和-=用在可变集合时会改变他们的值,使用在只读集合时,会返回一个修改了的拷贝集合。(这意味着只有当可读集合的引用是var才可以使用+=和-=)

3.重载一元操作符

​ 一元运算符定义的方法和前面看到的是相同的,重载一元操作符的函数不需要任何参数

下表是所有可以重载的一元运算符

表达式函数名+ aunaryPlus- aunaryMinus! anot++ a,a ++inc-- a,a --dec

+a直接返回a的值;-a直接返回a的负数值;!a如果a是Boolean类型的话就直接返回a的相反布尔值

二.重载比较运算符

​ 正如算数运算符一样,Kotlin中允许你将比较运算符(==,!=,>,<等等)用在任何对象上,而不仅仅是基本数据类型

1.相等运算符:equals

​ ==操作符在Kotlin中会转换为equals()函数的调用,!=也是对equals()函数的调用,只是结果相反。另外,相等性操作符的操作数是可空的,因为要比较null和相等性。a == b会先比较a是否为null,再调用a.equals(b)

​ equals函数被标记为override,不像其他操作符的约定,不需要加operator标识符,因为他是实现在Any类中的,相等性比价对于任何Kotlin类都是适用的

2.排序运算符:compareTo

​ 在Java类中,类进行查找最大值或者排序时,需要实现Comparable接口。而且进行比较时,没有简短的语法需要显式的调用element1.compareTo(element2)进行比较。

​ Kotlin也支持Comparable接口,但是接口中的compareTo方法可以通过约定调用:使用<,>,<=,和>=时会转化为调用compareTo方法。compareTo的返回值为Int,表达式p1

//实现Comparable接口,Person对象在Kotlin和Java中都能用来比较排序等操作

//这里先比较Person的firstName,如果firstName相同再比较lastName

data class Person(

val firstName: String, val lastName: String

) : Comparable {

override fun compareTo(other: Person): Int {

return compareValuesBy(this, other, Person::firstName, Person::lastName)

}

}

val person = Person("Li", "m1Ku")

val person2 = Person("wang", "rick")

println(person < person2)

>> true

compareValuesBy函数可以让你简单方便的实现compareTo方法,这个函数接收要被比价计算值的回调。这个函数会调用每一个回调,并且比较值。如果值不同,那么返回比较结果,如果相同就调用下一个回调或者如果没有更多回调时会返回0。回调可以是lambda表达式或者是属性引用

三.集合和序列的约定

​ 通过索引获取元素或者为集合元素赋值,还有检查一个元素是否属于一个集合都是最常见的集合操作。这些操作都可以使用操作符语句,并且也可以为自己的类定义这些操作符。

1.通过索引获取元素:get和set

​ map元素的取值和赋值都可以通过[]中括号操作符完成

val params = hashMapOf("name" to "m1ku", "password" to "123456", "token" to "erwer3fg")

val name = params["name"]

params["password"] = "654321"

println(name)

println(params)

>> m1Ku

{name=m1ku, password=654321, token=erwer3fg}

​ Kotlin中,索引操作符一个约定。使用索引操作符获取一个元素会转换为调用get方法,微元素设置会转化为调用set方法。Map和MutableMap中已经定义了这样的方法。

​ 如何在自己的类中定义这样的操作符呢?

​ 我们需要做的就是定义一个由operator修饰的名字叫get的函数

//定义所以操作符函数,获取Point的x和y坐标

operator fun Point.get(index: Int): Int {

return when (index) {

0 -> x

1 -> y

else ->

throw IndexOutOfBoundsException()

}

}

val point = Point(10, 88)

//调用这个时,转化为调用get函数

println(point[1])

>> 88

​ 定义一个set函数能让我们已类似的方式为集合元素赋值

operator fun Point.set(index: Int, value: Int) {

when (index) {

0 -> x = value

1 -> y = value

else ->

throw IndexOutOfBoundsException()

}

}

val point = Point(10, 88)

//使用约定语句为元素赋值

point[0] = 100

println(point[0])

>> 100

set函数最后一个元素是赋值运算式右边的值,其他元素是方括号中给定的索引

2."in"约定

​ in操作符:判断一个对象是否属于一个集合,对应调用的函数是contains

operator fun Rectangle.contains(p: Point): Boolean {

return p.x in upperLeft.x until lowerRight.x &&

p.y in upperLeft.y until lowerRight.y

}

val rect = Rectangle(Point(10, 20), Point(50, 50))

println(Point(20, 30) in rect)

>> true

in右边是调用contains函数的对象,左边是传递给函数的参数

val point = Point(20,30)

//下面这两句是等价的

point in rect

rect.contains(point)

3.rangeTo约定

​ 使用..语句创建一个序列,其实..操作符是一种简单的调用rangeTo函数的方式。可以为自己的类定义一个rangTo函数,但是当实现了comparable接口的类不需要自己自己定义这个函数。Kotlin标准库为实现了comparable接口的类定义了rangeTo方法。

//Circle实现了comparable接口,可以调用rangeTo函数返回一个序列

//我们可以判断不同元素是否在序列中

val startC = Circle(10f)

val endC = Circle(200f)

val circle = Circle(5f)

val circleRange = startC..endC

println(circle in circleRange)

>> false

4.for循环的"iterator"约定

​ Kotlin的for循环和范围检查使用的都是in操作符,但是在这里的意义是不同的,这里用来执行迭代操作。在Kotlin中这也是一种约定,这意味着iterator方法可以定义为扩展函数。这就是为什么一个普通Java的String也可以进行迭代了:在String的超类CharSequence上定义了iterator扩展函数

​ 我们可以为自己的类定义iterator方法

operator fun ClosedRange.iterator(): Iterator =

object : Iterator {

var current = start

override fun hasNext(): Boolean {

return current <= endInclusive

}

override fun next(): Circle {

return current

}

}

四.解构声明和组件函数

​ 现在已经熟悉了约定的使用,现在看一下数据类的最后一个特点,解构声明。这个特性可以将一个复合值拆开并将其存储在不同的变量中。

val p = Point(10,20)

//声明x,y变量,并用p给他们初始化赋值

val(x,y) = p

println(x)

>> 10

​ 解构声明看起来和普通的变量声明很像,但是解构声明是将一组变量放在括号中。这里解构声明也是用到了约定。对于解构声明中的每一个变量,都会调用一个叫componentN的函数,N是变量声明的位置。

//上面的解构声明等价于下面两行代码

x = p.component1()

y = p.component2()

​ 对于数据类,编译器为主构造器中声明的每个属性生成了一个componentN函数

​ 对于有多个返回值的函数,使用解构声明是很方便的,我们可以将需要返回的值定义在一个类中,然后函数返回这个类,再使用解构声明就方便的获取到了需要的值

data class NameComponent(val name: String, val extension: String)

fun splitName(fullName: String): NameComponent {

val result = fullName.split(".")

return NameComponent(result[0], result[1])

}

val (name, extension) = splitName("kotlin实战.pdf")

println("name = $name extension = $extension")

>> name = kotlin实战 extension = pdf

​ Kotlin为集合和数组定义了componentN函数,所以集合可以直接使用解构声明。当集合大小已知时,可以简化为

fun splitName2(fullName: String): NameComponent {

val (name, extension) = fullName.split(".",limit = 2)

return NameComponent(name, extension)

}

​ Kotlin标准函数库允许我们通过解构声明获得容器中的前5个元素

1.解构声明和循环

​ 解构声明不止可以用在函数的顶层语句中,而且还可以用在其他可以声明变量的地方,比如:循环。

//遍历一个map

//这个例子使用了两次约定:迭代对象,解构声明

fun printEntry(map: Map) {

for ((key, value) in map) {

println("$key$value")

}

}

五.重用属性访问逻辑:委托属性

​ 委托属性依赖于约定,它是Kotlin一个独特的强有力的特性。这个特性实现的基础是委托:委托是一种设计模式,它可以将一个对象要执行的任务,委托给另一个对象执行。辅助执行的对象叫:委托。当把这种模式使用在属性上时,就可以把访问器的逻辑委托给一个辅助对象。

1.委托属性的基本操作

​ 属性委托的语法如下:

class Example {

var p: String by Delegate()

}

​ 这里属性p将它的访问器逻辑委托给Delegate类的一个对象,通过关键字by对其后的表达式求值来获取这个对象。根据约定,委托类必须有getValue和setValue方法。像往常一样,他们可以是成员函数也可以是扩展函数。

​ 可以把example.p当做普通属性使用,但它将调用Delegate类辅助属性的方法

//调用委托类的setValue方法

example.p = "hhahah"

//调用委托类的getValue方法

val value = example.p

2.使用委托属性:惰性初始化和 by lazy()

​ 惰性初始化,是一种常见的模式,直到第一次访问某个属性时,对象的一部分才会按需创建。当初始化过程占据很多的资源,并且当对象使用时这些数据并不会用到时,这种模式是很有用的。

class Person(val name: String) {

//使用lazy标准库函数实现委托

val emails by lazy { loadEmails(this) }

private fun loadEmails(person: Person): List {

println("初始化函数调用")

return listOf("1", "2")

}

}

val p = Person("m1Ku")

//当第一次使用这个属性时,属性才会初始化即惰性初始化

p.emails

>> 初始化函数调用

​ lazy函数返回一个包含适当签名的getValue的方法的对象,所以就可以和by关键字一起使用创建一个委托属性。lazy函数的参数一个lambda,执行初始化值的逻辑。lazy函数默认是线程安全的。

3.实现委托属性

class User {

var age: Int by Delegates.observable(18,

{ property, oldValue, newValue ->

println("${property.name} $oldValue $newValue")

})

}

val u1 = User()

u1.age = 10

>>age 18 10

Delegates.observable()包含两个参数:初始值和修改处理Handler,每次修改属性值都会调用Handler。

​ by函数右边不一定是创建实例。它可以是函数调用,另一个属性,或者其他表达式,只要这个表达式的值是一个对象:编译器可以以正确的类型参数调用getValue和setValue方法。

4.委托属性的转换规则

​ 总结一下委托属性的规则,假设有下面这个有委托属性的类:

class Foo {

var c: Type by MyDelegate()

}

​ MyDelegate的实例会被保存在一个隐藏属性中,我们用代表他。编译器会用一个KProperty类型的对象便是属性,我们且用代表他。编译器生成如下的代码:

class Foo {

private val = MyDelegate()

var c: Type

set(value: Type) = .setValue(c, , value)

get() = .getValue(c, )

}

因此每次获取属性时,其对应的setValue和getValue方法就会调用

5.在map中存储属性值

​ 另一个委托属性能派上用场的地方是:用在一个动态定义属性集的对象上。这样的对象叫做:自订对象(expando objects )。

class Fruit() {

val attributes = hashMapOf()

fun setAttribute(attrName: String, value: String) {

attributes[attrName] = value

}

//将map作为委托属性

val name: String by attributes

}

​ 可以直接在map后面使用by关键字,这是因为标准库为Map和MutableMap接口定义了getValue和setValue的扩展函数,属性的名字自动用在map中的键,属性的值就是其对应的map中的值

好文推荐

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: