Kotlin知識歸納(九) —— 約定

前序

??????Java在標(biāo)準(zhǔn)庫中,有一些與特定的類相關(guān)聯(lián)的語言特性。比如,實現(xiàn) java.lang.Iterable 接口的對象可以在forEach循環(huán)中使用。Kotlin也提供很多類似原理的特性,但是是通過調(diào)用特定的函數(shù),來實現(xiàn)特定的語言特性,這種技術(shù)稱之為約定。(例如,實現(xiàn)名為plus特殊方法的類,可以在該類的對象上使用 + 運(yùn)算符)

??????因為類實現(xiàn)的接口集是固定的,Kotlin不能為了實現(xiàn)某個語言特性,而修改現(xiàn)有的Java類。但也可以通過把任意約定的方法定義為Java類的擴(kuò)展方法,使其具備Kotlin約定的能力。

??????Kotlin不允許開發(fā)者自定義自己的運(yùn)算符,因為Kotlin限制了你能重載的運(yùn)算符,以及運(yùn)算符對應(yīng)的函數(shù)名稱。

算術(shù)運(yùn)算符重載

??????在Java中,只有基本數(shù)據(jù)類型才可以使用算術(shù)運(yùn)算符,String類型也僅局限于使用 + 運(yùn)算符,對于其他類不能使用算術(shù)運(yùn)算符。

??????Kotlin中使用約定最直接的例子就是算術(shù)運(yùn)算符,意味著只要實現(xiàn)約定對應(yīng)的方法,就可以對任意類型使用算數(shù)運(yùn)算符。約定對應(yīng)的方法都需要使用operator關(guān)鍵字修飾的,表示你將該方法作為相應(yīng)的約定的實現(xiàn)。

二元算術(shù)運(yùn)算符

運(yùn)算符 函數(shù)名 表達(dá)式 轉(zhuǎn)換
*(乘法運(yùn)算符) times a * b a.times(b)
/(除法運(yùn)算符) div a / b a.div(b)
%(取模運(yùn)算符) rem a % b a.rem(b)
+(加法運(yùn)算符) plus a + b a.plus(b)
-(減法運(yùn)算符) minus a - b a.minus(b)

對于自定義類型的算術(shù)運(yùn)算符,與基本數(shù)據(jù)類型的算術(shù)運(yùn)算符具有相同的優(yōu)先級。

??????operator函數(shù)不要求兩邊運(yùn)算數(shù)類型相同。但不可將兩邊運(yùn)算數(shù)進(jìn)行交換運(yùn)算,因為Kotlin不自動支持交換性。想要支持交換性,需要在兩邊的運(yùn)算類型中定義相應(yīng)的算術(shù)運(yùn)算符的函數(shù)。

??????Kotlin不要求返回值類型必須和運(yùn)算數(shù)類型相同。也允許對約定的函數(shù)進(jìn)行重載,即定義多個參數(shù)類型不同operator函數(shù)。

data class Point(var x:Int,var y:Int)

operator fun Point.plus (point: Point):Point{
    return Point(x + point.x,y + point.y)
}

//定義另類的operator函數(shù)
operator fun Point.plus (value: Int){
    println("x = ${x + value} y = ${y + value}")
}

fun main(args:Array<String>){
    val point1 = Point(3,4)
    val point2 = Point(3,4)
    println(point1 + point2)
    println(point1 + 1)
}

運(yùn)算符函數(shù)與Java

??????Java中調(diào)用Kotlin的運(yùn)算符非常簡單,只需要像普通函數(shù)一樣調(diào)用運(yùn)算符對應(yīng)的函數(shù)。但由于Java中沒有operator關(guān)鍵字,所以Java中定義約定的具體函數(shù)時,唯一的約束是需要參數(shù)的 類型 和 數(shù)量 匹配。

在Java中定義兩個加法運(yùn)算符的plus方法:

#daqi.java
public class Point {
    public int x;
    public int y;

    public Point(int x ,int y){
        this.x = x;
        this.y = y;
    }

    public Point plus(Point p){
        return  new Point(x + p.x, y + p.y);
    }

    public Point plus(int p){
        return  new Point(x + p, y + p);
    }

    @Override
    public String toString() {
        return "x = " + x + " , y = " + y;
    }
}

在Kotlin中為Java類聲明約定的擴(kuò)展函數(shù),并使用加法運(yùn)算符:

#daqiKotlin.kt

//將約定的函數(shù)聲明為Java類的擴(kuò)展函數(shù)
operator fun Point.plus(longNum:Long):Point{
    return Point(this.x + longNum.toInt(), this.y + longNum.toInt())
}

fun main(args:Array<String>){
    var point1 = Point(3,4)
    var point2 = Point(4,5)
    //使用Java定義的運(yùn)算符函數(shù)
    println(point1 + point2)
    println(point1 + 1)
    println(point2 + 1L)
}


image

??????擴(kuò)展函數(shù)可以很好的對現(xiàn)有的Java類添加Kotlin運(yùn)算符的能力,但還是要遵從擴(kuò)展函數(shù)不能訪問privateprotected修飾的屬性或方法的特性。

復(fù)合輔助運(yùn)算符

??????Kotlin除了支持簡單的算術(shù)運(yùn)算符重載,還支持復(fù)合賦值運(yùn)算符重載,即 += 、-=等復(fù)合賦值運(yùn)算符。

運(yùn)算符 函數(shù)名 表達(dá)式 轉(zhuǎn)換
*= timesAssign a *= b a.timesAssign(b)
/= divAssign a /= b a.divAssign(b)
%= remAssign a %= b a.remAssign(b)
+= plusAssign a += b a.plusAssign(b)
-= minusAssign a -= b a.minusAssign(b)

??????當(dāng)在某類型中定義了返回該類型的基本算術(shù)運(yùn)算符的operator函數(shù),且右側(cè)運(yùn)算數(shù)的類型符合該operator函數(shù)的參數(shù)的情況下,可以使用復(fù)合輔助運(yùn)算符。例如,定義不同參數(shù)類型的plus函數(shù):

operator fun Point.plus(point: Point):Point{
    x += point.x
    y += point.y
    return this
}

operator fun Point.plus(value: Int):Point{
    x += value
    y += value
    return this
}

借助plus函數(shù)使用 復(fù)合賦值運(yùn)算符+= :

fun main(args: Array<String>) {
    var point1 = Point(3,4)
    var point2 = Point(4,5)
    point2 += point1
    point2 += 1
}

??????這意味著,使用復(fù)合輔助運(yùn)算符時,基本算術(shù)運(yùn)算符的方法和復(fù)合賦值運(yùn)算符的方法都可能被調(diào)用。當(dāng)存在符合兩側(cè)運(yùn)算數(shù)類型的基本算術(shù)運(yùn)算符的operator方法和復(fù)合賦值運(yùn)算符的operator方法時,編譯器會報錯。解決辦法是:

  • 將運(yùn)算符轉(zhuǎn)換為對應(yīng)的operator方法,直接調(diào)用方法。
  • 用val替代var,使編譯器調(diào)用復(fù)合賦值運(yùn)算符的該operator方法(例如:plusAssign)

運(yùn)算符與集合

??????Kotlin標(biāo)準(zhǔn)庫中支持集合的使用 + 、- 、+= 和 -= 來對元素進(jìn)行增減。+ 和 - 運(yùn)算符總是返回一個新的集合,+= 和 -= 運(yùn)算符始終就地修改集合。

一元運(yùn)算符和位運(yùn)算符

運(yùn)算符 函數(shù)名 表達(dá)式 轉(zhuǎn)換
+ unaryPlus +a a.unaryPlus()
- unaryMinus -a a.unaryMinus()
! not !a a.not()
++ inc a++、++a a.inc()
-- dec a--、--a a.dec()

??????當(dāng)定義incdec函數(shù)來重載自增和自減運(yùn)算符時,編譯器會自動支持與普通數(shù)字類型的前綴和后綴自增運(yùn)算符相同的語義。例如,調(diào)用前綴形式 ++a,其步驟是:

  • 把 a.inc() 結(jié)果賦值給 a
  • 把 a 的新值作為表達(dá)式結(jié)果返回。

比較運(yùn)算符

??????與算術(shù)運(yùn)算符一樣,Kotlin允許對任意類型重載比較運(yùn)算符(==、!=、>、<等)??梢灾苯邮褂眠\(yùn)算符進(jìn)行比較,不用像Java調(diào)用equalscompareTo函數(shù)。

等號運(yùn)算符

??????如果在Kotlin中使用 == 運(yùn)算符,它將被轉(zhuǎn)換成equals方法的調(diào)用。!=運(yùn)算符也會被轉(zhuǎn)換為equals方法的調(diào)用,但結(jié)果會取反。

??????與其他運(yùn)算符不同,== 和 != 可以用于可空運(yùn)算數(shù),因為這些運(yùn)算符會檢查運(yùn)算數(shù)是否為null。null == null 總是為 true。

表達(dá)式 轉(zhuǎn)換
a == b a?.equals(b) ?: (b === null)
a != b !(a?.equals(b) ?: (b === null))

??????當(dāng)自定義重載equals函數(shù)時,可以參考data類自動生成的equals函數(shù):

public boolean equals(@Nullable Object var1) {
  if (this != var1) {
     if (var1 instanceof Point) {
        Point var2 = (Point)var1;
        if (this.x == var2.x && this.y == var2.y) {
           return true;
        }
     }
     return false;
  } else {
     return true;
  }
}
  • 當(dāng)比較自身對象時,直接返回true。
  • 類型不同,則直接返回false。
  • 依據(jù)關(guān)鍵字段進(jìn)行判斷,條件符合就返回true。

??????Kotlin提供恒等運(yùn)算符(===)來檢查兩個參數(shù)是否是同一個對象的引用,與Java的==運(yùn)算符相同。但===!==(同一性檢查)不可重載,因此不存在對他們的約定。

??????== 運(yùn)算符和 != 運(yùn)算符只使用函數(shù) equals(other: Any?): Boolean,可以覆蓋它來提供自定義的相等性檢測實現(xiàn)。不會調(diào)用任何其他同名函數(shù)(如 equals(other: Point))或 擴(kuò)展函數(shù),因為繼承自Any類的實現(xiàn)始終優(yōu)先于擴(kuò)展函數(shù)和其他同名函數(shù)。

排序運(yùn)算符

??????在Java中,類可以實現(xiàn)Comparable接口,并在compareTo方法中判斷一個對象是否大于另一個對象。但只有基本數(shù)據(jù)類型可以使用 <>來比較,所有其他類型沒有簡明的語法調(diào)用compareTo方法,需要顯式調(diào)用。

??????Kotlin支持相同的Comparable接口(無論是Java的還是Kotlin的Comparable接口),比較運(yùn)算符將會被轉(zhuǎn)換為compareTo方法。所有在Java中實現(xiàn)Comparable接口的類,都可以在Kotlin中使用比較運(yùn)算符。

表達(dá)式 轉(zhuǎn)換
a > b a.compareTo(b) > 0
a < b a.compareTo(b) < 0
a >= b a.compareTo(b) >= 0
a <= b a.compareTo(b) <= 0

??????Kotlin標(biāo)準(zhǔn)庫中提供compareValuesBy函數(shù)來簡潔地實現(xiàn)compareTo方法。該方法接收兩個進(jìn)行比較的對象,和用于比較的數(shù)值的方法引用:

data class Point(var x:Int,var y:Int):Comparable<Point>{
    override fun compareTo(other: Point): Int {
        return compareValuesBy(this,other,Point::x,Point::y)
    }
}

fun main(args: Array<String>) {
    val point1 = Point(3,4)
    var point2 = Point(4,5)

    println("result = ${point1 < point2}")
}

equals方法和compareTo方法,在父類中已經(jīng)添加operator,重載時無需添加。

集合與區(qū)間的約定

??????處理結(jié)合最常見的是通過下標(biāo)獲取和設(shè)置元素,以及檢查元素是否屬于當(dāng)前集合。而這些操作在Kotlin中都提供相應(yīng)的運(yùn)算符語法支持:

  • 使用下標(biāo)運(yùn)算符a[b],獲取或設(shè)置元素。
  • 使用in運(yùn)算符,檢查元素是否在集合或區(qū)間內(nèi),也可以用于迭代。

下標(biāo)運(yùn)算符

??????使用下標(biāo)運(yùn)算符讀取元素會被轉(zhuǎn)換成get運(yùn)算符方法的調(diào)用。當(dāng)寫入元素時,將調(diào)用set。

表達(dá)式 轉(zhuǎn)換
a[i] a.get(i)
a[i_1, ……, i_n] a.get(i_1, ……, i_n)
a[i] = b a.set(i, b)
a[i_1, ……, i_n] = b a.set(i_1, ……, i_n, b)

??????Map也可以使用下標(biāo)運(yùn)算符,將鍵作為下標(biāo)傳入到下標(biāo)運(yùn)算符中獲取對應(yīng)的value。對于可變的map,同樣可以使用下標(biāo)運(yùn)算符修改對應(yīng)鍵的value值。

注:get的參數(shù)可以是任意類型,所以當(dāng)對map使用下標(biāo)運(yùn)算符時,參數(shù)類型時鍵的類型。

in運(yùn)算符

??????in運(yùn)算符用于檢查某個對象是否屬于集合。它是一種約定,相應(yīng)的函數(shù)為contains。

表達(dá)式 轉(zhuǎn)換
a in c c.contains(a)

rangTo 約定

??????當(dāng)需要創(chuàng)建區(qū)間時,都是使用..運(yùn)算符。..運(yùn)算符是調(diào)用rangeTo函數(shù)的一種約定。

表達(dá)式 轉(zhuǎn)換
start..end start.rangeTo(end)

可以為任何類定義rangeTo函數(shù)。但是,如果該類實現(xiàn)了Comparable接口,那么可以直接使用Kotlin標(biāo)準(zhǔn)庫為Comparable接口提供的rangeTo函數(shù)來創(chuàng)建一個區(qū)間。

public operator fun <T : Comparable<T>> T.rangeTo(that: T): ClosedRange<T> = ComparableRange(this, that)

使用Java8的LocalDate來構(gòu)建一個日期的區(qū)間:

fun main(args: Array<String>) {
    val now = LocalDate.now()
    val vacation = now .. now.plusDays(10)
    println(now.plusWeeks(1) in vacation)
}

..運(yùn)算符注意點:

  • ..運(yùn)算符的優(yōu)先級低于算術(shù)運(yùn)算符,但最好還是把參數(shù)括起來以避免混淆:
0 .. (n + 1)
  • 區(qū)別表達(dá)式調(diào)用函數(shù)式Api時,必須先將區(qū)間表達(dá)式括起來,否則編譯將不通過:
(0..10).filter { 
    it % 2 == 0
}.map { 
    it * it
}.forEach { 
    println(it)
}

iterator 約定

??????for循環(huán)中可以使用in運(yùn)算符來表示執(zhí)行迭代。這意味著Kotlin的for循環(huán)將被轉(zhuǎn)換成list.iterator()的調(diào)用,然后反復(fù)調(diào)用hasNextnext 方法。

iterator方法也是Kotlin中的一種約定,這意味iterator()可以被定義為擴(kuò)展函數(shù)。例如:Kotlin標(biāo)準(zhǔn)庫中為Java的CharSequence定義了一個擴(kuò)展函數(shù)iterator,使我們能遍歷一個常規(guī)的Java字符串。

for(s in "daqi"){
    
}

解構(gòu)聲明

??????Kotlin提供解構(gòu)聲明,允許你展開單個復(fù)合值,并使用它來初始化多個單獨的變量。

fun main(args: Array<String>) {
    val point = Point(3,4)
    val(x,y) = point
}   

??????解構(gòu)聲明看起來像普通的變量聲明,但他的括號中存在多個變量。但其實解構(gòu)聲明也是使用了約定的原理,要在解構(gòu)聲明中初始化每個變量,需要提供對應(yīng)的componentN函數(shù)(其中N是聲明中變量的位置)。

val point = Point(3,4)
val x = point.component1()
val y = point.component2()
image

數(shù)據(jù)類

??????Kotlin中提供一種很方便生成數(shù)據(jù)容器的方法,那就是將類聲明為數(shù)據(jù)類,也就是data類。

編譯器自動從數(shù)據(jù)類的主構(gòu)造函數(shù)中聲明的所有屬性生成以下方法:

  • equals()/hashCode()
  • toString()
  • componentN() 按聲明順序?qū)?yīng)于所有屬性
  • copy()

同時數(shù)據(jù)類必須滿足以下要求:

  • 主構(gòu)造函數(shù)需要至少有一個參數(shù)(可以使用默認(rèn)參數(shù)來實現(xiàn)無參主構(gòu)造函數(shù))
  • 主構(gòu)造函數(shù)的所有參數(shù)需要標(biāo)記為 val 或 var
  • 數(shù)據(jù)類不能是抽象、開放、密封或者內(nèi)部的

??????equals方法會檢查主構(gòu)造函數(shù)中聲明的所有屬性是否相等;hashCode()會根據(jù)主構(gòu)造函數(shù)中聲明的所有屬性生成一個哈希值;componentN()會按照主構(gòu)造函數(shù)中聲明的所有屬性的順序生成;toString()會按照以下格式"Point(x=3, y=4)"生成字符串。

??????數(shù)據(jù)類體中有顯式實現(xiàn) equals()、 hashCode() 或者 toString(),或者這些函數(shù)在父類中有 final 實現(xiàn),會使用現(xiàn)有函數(shù);數(shù)據(jù)類不允許為 componentN() 以及 copy() 函數(shù)提供顯式實現(xiàn)。

如果不使用數(shù)據(jù)類,需要手動聲明componentN()函數(shù):

class Point(val x :Int,val y: Int){
    operator fun component1() = x
    operator fun component21() = y
}

使用場景

  • 遍歷map

使用解構(gòu)聲明快速獲取mapentry 的鍵和值,快速遍歷。

for ((key, value) in map) {
   // 直接使用該 key、value
   
}
  • 從函數(shù)中返回多個變量

創(chuàng)建請求存儲返回信息的數(shù)據(jù)類,在調(diào)用方法獲取返回信息時,使用解構(gòu)聲明將其分成不同的值:

data class Result(val resultCode: Int, val status: Int,val body:String)
fun getHttpResult(……): Result {
    // 各種計算

    return Result(resultCode, status,josnBody)
}

------------------------------------------------------------------
//獲取返回值
val(resultCode, status,josnBody) = getHttpResult()
  • 在 lambda 表達(dá)式中解構(gòu)

和map遍歷相識,就是將lambda中的Map.Entry參數(shù)進(jìn)行解構(gòu)聲明:

val map = mapOf(1 to 1)
map.mapValues { (key, value) -> 
    "key = $key ,value = $value "
}

注意

??????由于數(shù)據(jù)類中componentN()是按照主構(gòu)造函數(shù)中聲明的所有屬性的順序?qū)?yīng)生成的。也就是說component1()返回的是主構(gòu)造函數(shù)中聲明的第一個值,component2()返回的是主構(gòu)造函數(shù)中聲明的第二個值,以此類推。

對于解構(gòu)聲明中不需要的變量,可以用下劃線取代其名稱,Kotlin將不會調(diào)用相應(yīng)的 componentN()

fun main(args: Array<String>) {
    val point = Point(3,4)
    val(_,y) = point
    println(y)
}   

image

??????否則,你想要的值在主構(gòu)造函數(shù)中聲明在第二個位置,而你不是使用下劃線取代其名稱取代第一個變量的位置時,解構(gòu)聲明將使用component1()對值進(jìn)行賦值,你將得不到你想要的值。

fun main(args: Array<String>) {
    val point = Point(3,4)
    //y軸坐標(biāo)應(yīng)該是第二個位置,但由于沒有使用_占位,將使用component1()對其進(jìn)行賦值,也就是使用x軸坐標(biāo)對y坐標(biāo)進(jìn)行賦值。
    val(y) = point
    println(y)
}   
image

中輟調(diào)用

??????在提到解構(gòu)聲明的地方,往往伴隨著中輟調(diào)用的出現(xiàn)。但中輟調(diào)用并不是什么約定,是讓含有infix 關(guān)鍵字修飾的方法可以像基本算術(shù)運(yùn)算符一樣被調(diào)用。即忽略該調(diào)用函數(shù)的點與圓括號,將函數(shù)名放在目標(biāo)對象和參數(shù)之間。

//中輟調(diào)用
1 to "one"

//普通調(diào)用
1.to("one")

中綴函數(shù)必須滿足以下要求:

  • 成員函數(shù)或擴(kuò)展函數(shù)
  • 只有一個參數(shù)
  • 參數(shù)不得接受可變參數(shù)且不能有默認(rèn)值

使用場景

  • 區(qū)間

使用..運(yùn)算符創(chuàng)建的區(qū)間是一個閉區(qū)間,當(dāng)我們需要創(chuàng)建倒序區(qū)間或者半閉區(qū)間,甚至是設(shè)置區(qū)間步長時,所使用到的downTountilstep 其實都不是關(guān)鍵字,而是一個個使用infix 關(guān)鍵字修飾的方法,只是使用中輟調(diào)用來進(jìn)行呈現(xiàn)。

  • map

在創(chuàng)建map時,對key和vlaue使用中輟調(diào)用來添加元素,提高可讀性。

val map = mapOf("one" to 1,"two" to 2)

中輟調(diào)用優(yōu)先級

??????中綴函數(shù)調(diào)用的優(yōu)先級低于算術(shù)操作符、類型轉(zhuǎn)換以及 rangeTo 操作符。所以0 until n * 20 until (n * 2)等價。

??????但中綴函數(shù)調(diào)用的優(yōu)先級高于布爾操作符&& 與 ||、is 與 in 檢測以及其他一些操作符。所以7 in 0 until 107 in (0 until 10)等價。

參考資料:

android Kotlin系列:

Kotlin知識歸納(一) —— 基礎(chǔ)語法

Kotlin知識歸納(二) —— 讓函數(shù)更好調(diào)用

Kotlin知識歸納(三) —— 頂層成員與擴(kuò)展

Kotlin知識歸納(四) —— 接口和類

Kotlin知識歸納(五) —— Lambda

Kotlin知識歸納(六) —— 類型系統(tǒng)

Kotlin知識歸納(七) —— 集合

Kotlin知識歸納(八) —— 序列

Kotlin知識歸納(九) —— 約定

Kotlin知識歸納(十) —— 委托

Kotlin知識歸納(十一) —— 高階函數(shù)

Kotlin知識歸納(十二) —— 泛型

Kotlin知識歸納(十三) —— 注解

Kotlin知識歸納(十四) —— 反射

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

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