Java標(biāo)準(zhǔn)庫(kù)中有和類(lèi)相綁定的語(yǔ)言特性,例如實(shí)現(xiàn)Iterable接口的類(lèi)可以使用在for循環(huán)中。Kotlin中也有一些類(lèi)似的特性,與Java不同的是,不是和特定的類(lèi)綁定的,Kotlin中是與特定名字的函數(shù)綁定的。例如我們?cè)陬?lèi)中定義了一個(gè)方法叫
plus,我們就可以在這個(gè)類(lèi)的實(shí)例上使用+運(yùn)算符,這就叫做約定
1.重載算數(shù)運(yùn)算符
? Kotlin中最簡(jiǎn)單明了的使用約定的例子就是算數(shù)運(yùn)算符。在Java中算數(shù)運(yùn)算符只可以使用在基本數(shù)據(jù)類(lèi)型上,+號(hào)可以使用在String上。當(dāng)我們想在BigInteger類(lèi)上使用+,或者想使用+=添加元素到一個(gè)集合中時(shí)Java就做不到了。但是Kotlin中就可以做到。
1.重載二元算數(shù)運(yùn)算符
? 首先從+開(kāi)始,實(shí)現(xiàn)將兩個(gè)點(diǎn)的坐標(biāo)值加起來(lái),使用operator修飾符定義一個(gè)操作符函數(shù)
data class Point(val x: Int, val y: Int) {
operator fun plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}
}
? 定義Point對(duì)象,此時(shí)可以使用+號(hào)
val point = Point(10, 20)
val point2 = Point(20, 30)
println(point + point2)
>>Point(x=30, y=50)
? 也可以把操作符函數(shù)定義成擴(kuò)展函數(shù),而使用擴(kuò)展函數(shù)語(yǔ)法也會(huì)是一種通用的定義操作符擴(kuò)展函數(shù)的模板
operator fun Point.plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}
? Kotlin不允許你自己定義操作符,下面是可以重載的操作符及函數(shù)名。為自己寫(xiě)的Kotlin類(lèi)定義的算數(shù)運(yùn)算符其優(yōu)先級(jí)和數(shù)字運(yùn)算符是相同的。
| 表達(dá)式 | 函數(shù)名 |
|---|---|
| a * b | times |
| a / b | div |
| a % b | mod |
| a + b | plus |
| a - b | minus |
? 當(dāng)我們定義操作符時(shí),他接受的兩個(gè)操作符可以是不同類(lèi)型的,操作符函數(shù)的返回值也可以是不同類(lèi)型的。但是需要注意的是Kotlin操作符不支持左右兩個(gè)操作數(shù)交換順序
operator fun Point.times(scale: Double): Double {
return x * scale
}
2.重載復(fù)合賦值操作符
? 正常情況下,當(dāng)定義了一個(gè)操作符比如plus時(shí),Kotlin會(huì)同時(shí)支持+和+=操作符。+=和-=等等叫做復(fù)合賦值操作符
//復(fù)合賦值運(yùn)算符
var point3 = Point(1, 2)
point3 += Point(2, 4)
println(point3)
>> Point(x=3, y=6)
? 如果你定義一個(gè)叫空返回值的plusAssign的函數(shù),當(dāng)時(shí)用+=操作符時(shí)就會(huì)調(diào)用這個(gè)函數(shù)。minusAssign,timesAssign也是類(lèi)似的。Kotlin標(biāo)準(zhǔn)庫(kù)為為可變集合定義了plusAssign函數(shù)。
? 當(dāng)你在代碼中使用+=時(shí),理論上plus和plusAssign都會(huì)被調(diào)用。我們應(yīng)該避免同時(shí)為添加plus和plusAssign操作符。如果你的類(lèi)時(shí)不可變得,你應(yīng)該只提供像plus一樣返回一個(gè)新值的操作符,如果設(shè)計(jì)一個(gè)可變的類(lèi),你應(yīng)該只需要提供一個(gè)plusAssign以及類(lèi)似的操作符。集合操作中,+和-會(huì)返回一個(gè)新的集合;+=和-=用在可變集合時(shí)會(huì)改變他們的值,使用在只讀集合時(shí),會(huì)返回一個(gè)修改了的拷貝集合。(這意味著只有當(dāng)可讀集合的引用是var才可以使用+=和-=)
3.重載一元操作符
? 一元運(yùn)算符定義的方法和前面看到的是相同的,重載一元操作符的函數(shù)不需要任何參數(shù)
下表是所有可以重載的一元運(yùn)算符
| 表達(dá)式 | 函數(shù)名 |
|---|---|
| + a | unaryPlus |
| - a | unaryMinus |
| ! a | not |
| ++ a,a ++ | inc |
| -- a,a -- | dec |
2.重載比較運(yùn)算符
? 正如算數(shù)運(yùn)算符一樣,Kotlin中允許你將比較運(yùn)算符(==,!=,>,<等等)用在任何對(duì)象上,而不僅僅是基本數(shù)據(jù)類(lèi)型
1.相等運(yùn)算符:equals
? ==操作符在Kotlin中會(huì)轉(zhuǎn)換為equals()函數(shù)的調(diào)用,!=也是對(duì)equals()函數(shù)的調(diào)用,只是結(jié)果相反。另外,相等性操作符的操作數(shù)是可空的,因?yàn)橐容^null和相等性。a == b會(huì)先比較a是否為null,再調(diào)用a.equals(b)
? equals函數(shù)被標(biāo)記為override,不像其他操作符的約定,不需要加operator標(biāo)識(shí)符,因?yàn)樗菍?shí)現(xiàn)在Any類(lèi)中的,相等性比價(jià)對(duì)于任何Kotlin類(lèi)都是適用的
2.排序運(yùn)算符:compareTo
? 在Java類(lèi)中,類(lèi)進(jìn)行查找最大值或者排序時(shí),需要實(shí)現(xiàn)Comparable接口。而且進(jìn)行比較時(shí),沒(méi)有簡(jiǎn)短的語(yǔ)法需要顯式的調(diào)用element1.compareTo(element2)進(jìn)行比較。
? Kotlin也支持Comparable接口,但是接口中的compareTo方法可以通過(guò)約定調(diào)用:使用<,>,<=,和>=時(shí)會(huì)轉(zhuǎn)化為調(diào)用compareTo方法。compareTo的返回值為Int,表達(dá)式p1<p2等價(jià)于p1.compareTo(p2) < 0,其他比較符也是相同的。Comparable和equals一樣,也不需要operator操作符
//實(shí)現(xiàn)Comparable接口,Person對(duì)象在Kotlin和Java中都能用來(lái)比較排序等操作
//這里先比較Person的firstName,如果firstName相同再比較lastName
data class Person(
val firstName: String, val lastName: String
) : Comparable<Person> {
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函數(shù)可以讓你簡(jiǎn)單方便的實(shí)現(xiàn)compareTo方法,這個(gè)函數(shù)接收要被比價(jià)計(jì)算值的回調(diào)。這個(gè)函數(shù)會(huì)調(diào)用每一個(gè)回調(diào),并且比較值。如果值不同,那么返回比較結(jié)果,如果相同就調(diào)用下一個(gè)回調(diào)或者如果沒(méi)有更多回調(diào)時(shí)會(huì)返回0?;卣{(diào)可以是lambda表達(dá)式或者是屬性引用
3.集合和序列的約定
? 通過(guò)索引獲取元素或者為集合元素賦值,還有檢查一個(gè)元素是否屬于一個(gè)集合都是最常見(jiàn)的集合操作。這些操作都可以使用操作符語(yǔ)句,并且也可以為自己的類(lèi)定義這些操作符。
1.通過(guò)索引獲取元素:get和set
? map元素的取值和賦值都可以通過(guò)[]中括號(hào)操作符完成
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中,索引操作符一個(gè)約定。使用索引操作符獲取一個(gè)元素會(huì)轉(zhuǎn)換為調(diào)用get方法,微元素設(shè)置會(huì)轉(zhuǎn)化為調(diào)用set方法。Map和MutableMap中已經(jīng)定義了這樣的方法。
? 如何在自己的類(lèi)中定義這樣的操作符呢?
? 我們需要做的就是定義一個(gè)由operator修飾的名字叫get的函數(shù)
//定義所以操作符函數(shù),獲取Point的x和y坐標(biāo)
operator fun Point.get(index: Int): Int {
return when (index) {
0 -> x
1 -> y
else ->
throw IndexOutOfBoundsException()
}
}
val point = Point(10, 88)
//調(diào)用這個(gè)時(shí),轉(zhuǎn)化為調(diào)用get函數(shù)
println(point[1])
>> 88
? 定義一個(gè)set函數(shù)能讓我們已類(lèi)似的方式為集合元素賦值
operator fun Point.set(index: Int, value: Int) {
when (index) {
0 -> x = value
1 -> y = value
else ->
throw IndexOutOfBoundsException()
}
}
val point = Point(10, 88)
//使用約定語(yǔ)句為元素賦值
point[0] = 100
println(point[0])
>> 100
set函數(shù)最后一個(gè)元素是賦值運(yùn)算式右邊的值,其他元素是方括號(hào)中給定的索引
2."in"約定
? in操作符:判斷一個(gè)對(duì)象是否屬于一個(gè)集合,對(duì)應(yīng)調(diào)用的函數(shù)是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右邊是調(diào)用contains函數(shù)的對(duì)象,左邊是傳遞給函數(shù)的參數(shù)
val point = Point(20,30)
//下面這兩句是等價(jià)的
point in rect
rect.contains(point)
3.rangeTo約定
? 使用..語(yǔ)句創(chuàng)建一個(gè)序列,其實(shí)..操作符是一種簡(jiǎn)單的調(diào)用rangeTo函數(shù)的方式??梢詾樽约旱念?lèi)定義一個(gè)rangTo函數(shù),但是當(dāng)實(shí)現(xiàn)了comparable接口的類(lèi)不需要自己自己定義這個(gè)函數(shù)。Kotlin標(biāo)準(zhǔn)庫(kù)為實(shí)現(xiàn)了comparable接口的類(lèi)定義了rangeTo方法。
//Circle實(shí)現(xiàn)了comparable接口,可以調(diào)用rangeTo函數(shù)返回一個(gè)序列
//我們可以判斷不同元素是否在序列中
val startC = Circle(10f)
val endC = Circle(200f)
val circle = Circle(5f)
val circleRange = startC..endC
println(circle in circleRange)
>> false
4.for循環(huán)的"iterator"約定
? Kotlin的for循環(huán)和范圍檢查使用的都是in操作符,但是在這里的意義是不同的,這里用來(lái)執(zhí)行迭代操作。在Kotlin中這也是一種約定,這意味著iterator方法可以定義為擴(kuò)展函數(shù)。這就是為什么一個(gè)普通Java的String也可以進(jìn)行迭代了:在String的超類(lèi)CharSequence上定義了iterator擴(kuò)展函數(shù)
? 我們可以為自己的類(lèi)定義iterator方法
operator fun ClosedRange<Circle>.iterator(): Iterator<Circle> =
object : Iterator<Circle> {
var current = start
override fun hasNext(): Boolean {
return current <= endInclusive
}
override fun next(): Circle {
return current
}
}
4.解構(gòu)聲明和組件函數(shù)
? 現(xiàn)在已經(jīng)熟悉了約定的使用,現(xiàn)在看一下數(shù)據(jù)類(lèi)的最后一個(gè)特點(diǎn),解構(gòu)聲明。這個(gè)特性可以將一個(gè)復(fù)合值拆開(kāi)并將其存儲(chǔ)在不同的變量中。
val p = Point(10,20)
//聲明x,y變量,并用p給他們初始化賦值
val(x,y) = p
println(x)
>> 10
? 解構(gòu)聲明看起來(lái)和普通的變量聲明很像,但是解構(gòu)聲明是將一組變量放在括號(hào)中。這里解構(gòu)聲明也是用到了約定。對(duì)于解構(gòu)聲明中的每一個(gè)變量,都會(huì)調(diào)用一個(gè)叫componentN的函數(shù),N是變量聲明的位置。
//上面的解構(gòu)聲明等價(jià)于下面兩行代碼
x = p.component1()
y = p.component2()
? 對(duì)于數(shù)據(jù)類(lèi),編譯器為主構(gòu)造器中聲明的每個(gè)屬性生成了一個(gè)componentN函數(shù)
? 對(duì)于有多個(gè)返回值的函數(shù),使用解構(gòu)聲明是很方便的,我們可以將需要返回的值定義在一個(gè)類(lèi)中,然后函數(shù)返回這個(gè)類(lèi),再使用解構(gòu)聲明就方便的獲取到了需要的值
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實(shí)戰(zhàn).pdf")
println("name = $name extension = $extension")
>> name = kotlin實(shí)戰(zhàn) extension = pdf
? Kotlin為集合和數(shù)組定義了componentN函數(shù),所以集合可以直接使用解構(gòu)聲明。當(dāng)集合大小已知時(shí),可以簡(jiǎn)化為
fun splitName2(fullName: String): NameComponent {
val (name, extension) = fullName.split(".",limit = 2)
return NameComponent(name, extension)
}
? Kotlin標(biāo)準(zhǔn)函數(shù)庫(kù)允許我們通過(guò)解構(gòu)聲明獲得容器中的前5個(gè)元素
1.解構(gòu)聲明和循環(huán)
? 解構(gòu)聲明不止可以用在函數(shù)的頂層語(yǔ)句中,而且還可以用在其他可以聲明變量的地方,比如:循環(huán)。
//遍歷一個(gè)map
//這個(gè)例子使用了兩次約定:迭代對(duì)象,解構(gòu)聲明
fun printEntry(map: Map<String, String>) {
for ((key, value) in map) {
println("$key$value")
}
}
?
5.重用屬性訪問(wèn)邏輯:委托屬性
? 委托屬性依賴(lài)于約定,它是Kotlin一個(gè)獨(dú)特的強(qiáng)有力的特性。這個(gè)特性實(shí)現(xiàn)的基礎(chǔ)是委托:委托是一種設(shè)計(jì)模式,它可以將一個(gè)對(duì)象要執(zhí)行的任務(wù),委托給另一個(gè)對(duì)象執(zhí)行。輔助執(zhí)行的對(duì)象叫:委托。當(dāng)把這種模式使用在屬性上時(shí),就可以把訪問(wèn)器的邏輯委托給一個(gè)輔助對(duì)象。
1.委托屬性的基本操作
? 屬性委托的語(yǔ)法如下:
class Example {
var p: String by Delegate()
}
? 這里屬性p將它的訪問(wèn)器邏輯委托給Delegate類(lèi)的一個(gè)對(duì)象,通過(guò)關(guān)鍵字by對(duì)其后的表達(dá)式求值來(lái)獲取這個(gè)對(duì)象。根據(jù)約定,委托類(lèi)必須有getValue和setValue方法。像往常一樣,他們可以是成員函數(shù)也可以是擴(kuò)展函數(shù)。
? 可以把example.p當(dāng)做普通屬性使用,但它將調(diào)用Delegate類(lèi)輔助屬性的方法
//調(diào)用委托類(lèi)的setValue方法
example.p = "hhahah"
//調(diào)用委托類(lèi)的getValue方法
val value = example.p
2.使用委托屬性:惰性初始化和 by lazy()
? 惰性初始化,是一種常見(jiàn)的模式,直到第一次訪問(wèn)某個(gè)屬性時(shí),對(duì)象的一部分才會(huì)按需創(chuàng)建。當(dāng)初始化過(guò)程占據(jù)很多的資源,并且當(dāng)對(duì)象使用時(shí)這些數(shù)據(jù)并不會(huì)用到時(shí),這種模式是很有用的。
class Person(val name: String) {
//使用lazy標(biāo)準(zhǔn)庫(kù)函數(shù)實(shí)現(xiàn)委托
val emails by lazy { loadEmails(this) }
private fun loadEmails(person: Person): List<String> {
println("初始化函數(shù)調(diào)用")
return listOf("1", "2")
}
}
val p = Person("m1Ku")
//當(dāng)?shù)谝淮问褂眠@個(gè)屬性時(shí),屬性才會(huì)初始化即惰性初始化
p.emails
>> 初始化函數(shù)調(diào)用
? lazy函數(shù)返回一個(gè)包含適當(dāng)簽名的getValue的方法的對(duì)象,所以就可以和by關(guān)鍵字一起使用創(chuàng)建一個(gè)委托屬性。lazy函數(shù)的參數(shù)一個(gè)lambda,執(zhí)行初始化值的邏輯。lazy函數(shù)默認(rèn)是線程安全的。
3.實(shí)現(xiàn)委托屬性
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()包含兩個(gè)參數(shù):初始值和修改處理Handler,每次修改屬性值都會(huì)調(diào)用Handler。
? by函數(shù)右邊不一定是創(chuàng)建實(shí)例。它可以是函數(shù)調(diào)用,另一個(gè)屬性,或者其他表達(dá)式,只要這個(gè)表達(dá)式的值是一個(gè)對(duì)象:編譯器可以以正確的類(lèi)型參數(shù)調(diào)用getValue和setValue方法。
4.委托屬性的轉(zhuǎn)換規(guī)則
? 總結(jié)一下委托屬性的規(guī)則,假設(shè)有下面這個(gè)有委托屬性的類(lèi):
class Foo {
var c: Type by MyDelegate()
}
? MyDelegate的實(shí)例會(huì)被保存在一個(gè)隱藏屬性中,我們用<delegate>代表他。編譯器會(huì)用一個(gè)KProperty類(lèi)型的對(duì)象便是屬性,我們且用<property>代表他。編譯器生成如下的代碼:
class Foo {
private val <delegate> = MyDelegate()
var c: Type
set(value: Type) = <delegate>.setValue(c, <property>, value)
get() = <delegate>.getValue(c, <property>)
}
因此每次獲取屬性時(shí),其對(duì)應(yīng)的setValue和getValue方法就會(huì)調(diào)用
5.在map中存儲(chǔ)屬性值
? 另一個(gè)委托屬性能派上用場(chǎng)的地方是:用在一個(gè)動(dòng)態(tài)定義屬性集的對(duì)象上。這樣的對(duì)象叫做:自訂對(duì)象(expando objects )。
class Fruit() {
val attributes = hashMapOf<String, String>()
fun setAttribute(attrName: String, value: String) {
attributes[attrName] = value
}
//將map作為委托屬性
val name: String by attributes
}
? 可以直接在map后面使用by關(guān)鍵字,這是因?yàn)闃?biāo)準(zhǔn)庫(kù)為Map和MutableMap接口定義了getValue和setValue的擴(kuò)展函數(shù),屬性的名字自動(dòng)用在map中的鍵,屬性的值就是其對(duì)應(yīng)的map中的值。
?