Kotlin學(xué)習(xí)(7)重載操作符和其他約定

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)必須有getValuesetValue方法。像往常一樣,他們可以是成員函數(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)的setValuegetValue方法就會(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ù)為MapMutableMap接口定義了getValuesetValue的擴(kuò)展函數(shù),屬性的名字自動(dòng)用在map中的鍵,屬性的值就是其對(duì)應(yīng)的map中的值。

?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容