Kotlin知識概括(九) —— 約定

前序

      Java在標準庫中,有一些與特定的類相關聯的語言特性。好比,實現 java.lang.Iterable 接口的對象能夠在forEach循環中使用。Kotlin也提供不少相似原理的特性,可是是經過調用特定的函數,來實現特定的語言特性,這種技術稱之爲約定。(例如,實現名爲plus特殊方法的類,能夠在該類的對象上使用 + 運算符)java

      由於類實現的接口集是固定的,Kotlin不能爲了實現某個語言特性,而修改現有的Java類。但也能夠經過把任意約定的方法定義爲Java類的擴展方法,使其具有Kotlin約定的能力。android

      Kotlin不容許開發者自定義本身的運算符,由於Kotlin限制了你能重載的運算符,以及運算符對應的函數名稱。bash

算術運算符重載

      在Java中,只有基本數據類型纔可使用算術運算符,String類型也僅侷限於使用 + 運算符,對於其餘類不能使用算術運算符。ide

      Kotlin中使用約定最直接的例子就是算術運算符,意味着只要實現約定對應的方法,就能夠對任意類型使用算數運算符。約定對應的方法都須要使用operator關鍵字修飾的,表示你將該方法做爲相應的約定的實現。函數

二元算術運算符

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

對於自定義類型的算術運算符,與基本數據類型的算術運算符具備相同的優先級。post

      operator函數不要求兩邊運算數類型相同。但不可將兩邊運算數進行交換運算,由於Kotlin不自動支持交換性。想要支持交換性,須要在兩邊的運算類型中定義相應的算術運算符的函數。ui

      Kotlin不要求返回值類型必須和運算數類型相同。也容許對約定的函數進行重載,即定義多個參數類型不一樣operator函數。this

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函數
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)
}
複製代碼

運算符函數與Java

      Java中調用Kotlin的運算符很是簡單,只須要像普通函數同樣調用運算符對應的函數。但因爲Java中沒有operator關鍵字,因此Java中定義約定的具體函數時,惟一的約束是須要參數的 類型 和 數量 匹配。spa

在Java中定義兩個加法運算符的plus方法:.net

#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類聲明約定的擴展函數,並使用加法運算符:

#daqiKotlin.kt

//將約定的函數聲明爲Java類的擴展函數
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定義的運算符函數
    println(point1 + point2)
    println(point1 + 1)
    println(point2 + 1L)
}


複製代碼

      擴展函數能夠很好的對現有的Java類添加Kotlin運算符的能力,但仍是要聽從擴展函數不能訪問privateprotected修飾的屬性或方法的特性。

複合輔助運算符

      Kotlin除了支持簡單的算術運算符重載,還支持複合賦值運算符重載,即 += 、-=等複合賦值運算符。

運算符 函數名 表達式 轉換
*= 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)

      當在某類型中定義了返回該類型的基本算術運算符的operator函數,且右側運算數的類型符合該operator函數的參數的狀況下,可使用複合輔助運算符。例如,定義不一樣參數類型的plus函數:

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函數使用 複合賦值運算符+= :

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

      這意味着,使用複合輔助運算符時,基本算術運算符的方法和複合賦值運算符的方法均可能被調用。當存在符合兩側運算數類型的基本算術運算符的operator方法和複合賦值運算符的operator方法時,編譯器會報錯。解決辦法是:

  • 將運算符轉換爲對應的operator方法,直接調用方法。
  • 用val替代var,使編譯器調用複合賦值運算符的該operator方法(例如:plusAssign)

運算符與集合

      Kotlin標準庫中支持集合的使用 + 、- 、+= 和 -= 來對元素進行增減。+ 和 - 運算符老是返回一個新的集合,+= 和 -= 運算符始終就地修改集合

一元運算符和位運算符

運算符 函數名 表達式 轉換
+ unaryPlus +a a.unaryPlus()
- unaryMinus -a a.unaryMinus()
! not !a a.not()
++ inc a++、++a a.inc()
-- dec a--、--a a.dec()

      當定義incdec函數來重載自增和自減運算符時,編譯器會自動支持與普通數字類型的前綴和後綴自增運算符相同的語義。例如,調用前綴形式 ++a,其步驟是:

  • 把 a.inc() 結果賦值給 a
  • 把 a 的新值做爲表達式結果返回。

比較運算符

      與算術運算符同樣,Kotlin容許對任意類型重載比較運算符(==、!=、>、<等)。能夠直接使用運算符進行比較,不用像Java調用equalscompareTo函數。

等號運算符

      若是在Kotlin中使用 == 運算符,它將被轉換成equals方法的調用。!=運算符也會被轉換爲equals方法的調用,但結果會取反。

      與其餘運算符不一樣,== 和 != 能夠用於可空運算數,由於這些運算符會檢查運算數是否爲null。null == null 老是爲 true。

表達式 轉換
a == b a?.equals(b) ?: (b === null)
a != b !(a?.equals(b) ?: (b === null))

      當自定義重載equals函數時,能夠參考data類自動生成的equals函數:

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;
  }
}
複製代碼
  • 當比較自身對象時,直接返回true。
  • 類型不一樣,則直接返回false。
  • 依據關鍵字段進行判斷,條件符合就返回true。

      Kotlin提供恆等運算符(===)來檢查兩個參數是不是同一個對象的引用,與Java的==運算符相同。但===!==(同一性檢查)不可重載,所以不存在對他們的約定。

      == 運算符和 != 運算符只使用函數 equals(other: Any?): Boolean,能夠覆蓋它來提供自定義的相等性檢測實現。不會調用任何其餘同名函數(如 equals(other: Point))或 擴展函數,由於繼承自Any類的實現始終優先於擴展函數和其餘同名函數

排序運算符

      在Java中,類能夠實現Comparable接口,並在compareTo方法中判斷一個對象是否大於另外一個對象。但只有基本數據類型可使用 <>來比較,全部其餘類型沒有簡明的語法調用compareTo方法,須要顯式調用。

      Kotlin支持相同的Comparable接口(不管是Java的仍是Kotlin的Comparable接口),比較運算符將會被轉換爲compareTo方法。全部在Java中實現Comparable接口的類,均可以在Kotlin中使用比較運算符。

表達式 轉換
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標準庫中提供compareValuesBy函數來簡潔地實現compareTo方法。該方法接收兩個進行比較的對象,和用於比較的數值的方法引用:

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方法,在父類中已經添加operator,重載時無需添加。

集合與區間的約定

      處理結合最多見的是經過下標獲取和設置元素,以及檢查元素是否屬於當前集合。而這些操做在Kotlin中都提供相應的運算符語法支持:

  • 使用下標運算符a[b],獲取或設置元素。
  • 使用in運算符,檢查元素是否在集合或區間內,也能夠用於迭代。

下標運算符

      使用下標運算符讀取元素會被轉換成get運算符方法的調用。當寫入元素時,將調用set

表達式 轉換
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也可使用下標運算符,將鍵做爲下標傳入到下標運算符中獲取對應的value。對於可變的map,一樣可使用下標運算符修改對應鍵的value值。

注:get的參數能夠是任意類型,因此當對map使用下標運算符時,參數類型時鍵的類型。

in運算符

      in運算符用於檢查某個對象是否屬於集合。它是一種約定,相應的函數爲contains

表達式 轉換
a in c c.contains(a)

rangTo 約定

      當須要建立區間時,都是使用..運算符。..運算符是調用rangeTo函數的一種約定。

表達式 轉換
start..end start.rangeTo(end)

能夠爲任何類定義rangeTo函數。可是,若是該類實現了Comparable接口,那麼能夠直接使用Kotlin標準庫爲Comparable接口提供的rangeTo函數來建立一個區間。

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

使用Java8的LocalDate來構建一個日期的區間:

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

..運算符注意點:

  • ..運算符的優先級低於算術運算符,但最好仍是把參數括起來以免混淆:
0 .. (n + 1)
複製代碼
  • 區間表達式調用函數式Api時,必須先將區間表達式括起來,不然編譯將不經過:
(0..10).filter { 
    it % 2 == 0
}.map { 
    it * it
}.forEach { 
    println(it)
}
複製代碼

iterator 約定

      for循環中可使用in運算符來表示執行迭代。這意味着Kotlin的for循環將被轉換成list.iterator()的調用,而後反覆調用hasNextnext 方法。

iterator方法也是Kotlin中的一種約定,這意味iterator()能夠被定義爲擴展函數。例如:Kotlin標準庫中爲Java的CharSequence定義了一個擴展函數iterator,使咱們能遍歷一個常規的Java字符串。

for(s in "daqi"){
    
}
複製代碼

解構聲明

      Kotlin提供解構聲明,容許你展開單個複合值,並使用它來初始化多個單獨的變量。

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

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

val point = Point(3,4)
val x = point.component1()
val y = point.component2()
複製代碼

數據類

      Kotlin中提供一種很方便生成數據容器的方法,那就是將類聲明爲數據類,也就是data類。

編譯器自動從數據類的主構造函數中聲明的全部屬性生成如下方法:

  • equals()/hashCode()
  • toString()
  • componentN() 按聲明順序對應於全部屬性
  • copy()

同時數據類必須知足如下要求:

  • 主構造函數須要至少有一個參數(可使用默認參數來實現無參主構造函數)
  • 主構造函數的全部參數須要標記爲 val 或 var
  • 數據類不能是抽象、開放、密封或者內部的

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

      數據類體中有顯式實現 equals()hashCode() 或者 toString(),或者這些函數在父類中有 final 實現,會使用現有函數;數據類不容許爲 componentN() 以及 copy() 函數提供顯式實現。

      若是該類不是數據類,要想該類的對象也能夠應用於解構聲明,須要手動聲明對應的operator修飾的componentN()函數(成員函數和擴展函數均可以):

fun main() {
    val(x,y) = Piont(1,2)

}

class Piont(val x:Int,val y:Int){
    operator fun component1():Int{
        return x
    }

    operator fun component2():Int{
        return y
    }
}
複製代碼

使用場景

  • 遍歷map

      使用解構聲明快速獲取mapentry 的鍵和值,快速遍歷。

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

      建立請求存儲返回信息的數據類,在調用方法獲取返回信息時,使用解構聲明將其分紅不一樣的值:

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()
複製代碼

      注意:咱們也可使用標準庫中的 Pair 類做爲返回值,來實現返回兩個變量。

  • 在 lambda 表達式中解構

      和map遍歷類似,就是將lambda中的Map.Entry參數進行解構聲明:

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

注意

      因爲數據類中componentN()是按照主構造函數中聲明的全部屬性的順序對應生成的。也就是說component1()返回的是主構造函數中聲明的第一個值,component2()返回的是主構造函數中聲明的第二個值,以此類推。

對於解構聲明中不須要的變量,能夠用下劃線取代其名稱,Kotlin將不會調用相應的 componentN()

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

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

fun main(args: Array<String>) {
    val point = Point(3,4)
    //y軸座標應該是第二個位置,但因爲沒有使用_佔位,將使用component1()對其進行賦值,也就是使用x軸座標對y座標進行賦值。
    val(y) = point
    println(y)
}   
複製代碼

中輟調用

      在提到解構聲明的地方,每每伴隨着中輟調用的出現。但中輟調用並非什麼約定,是讓含有infix 關鍵字修飾的方法,能夠像基本算術運算符同樣被調用。即忽略該調用函數的點與圓括號,將函數名放在目標對象和參數之間

//中輟調用
1 to "one"

//普通調用
1.to("one")
複製代碼

中綴函數必須知足如下要求:

  • 成員函數或擴展函數
  • 只有一個參數
  • 參數不得接受可變參數且不能有默認值

使用場景

  • 區間

使用..運算符建立的區間是一個閉區間,當咱們須要建立倒序區間或者半閉區間,甚至是設置區間步長時,所使用到的downTountilstep 其實都不是關鍵字,而是一個個使用infix 關鍵字修飾的方法,只是使用中輟調用來進行呈現。

  • map

在建立map時,對key和vlaue使用中輟調用來添加元素,提升可讀性。

val map = mapOf("one" to 1,"two" to 2)
複製代碼

中輟調用優先級

      中綴函數調用的優先級低於算術操做符、類型轉換以及 rangeTo 操做符。因此0 until n * 20 until (n * 2)等價。

      但中綴函數調用的優先級高於布爾操做符&& 與 ||、is 與 in 檢測以及其餘一些操做符。因此7 in 0 until 107 in (0 until 10)等價。

參考資料:

android Kotlin系列:

Kotlin知識概括(一) —— 基礎語法

Kotlin知識概括(二) —— 讓函數更好調用

Kotlin知識概括(三) —— 頂層成員與擴展

Kotlin知識概括(四) —— 接口和類

Kotlin知識概括(五) —— Lambda

Kotlin知識概括(六) —— 類型系統

Kotlin知識概括(七) —— 集合

Kotlin知識概括(八) —— 序列

Kotlin知識概括(九) —— 約定

Kotlin知識概括(十) —— 委託

Kotlin知識概括(十一) —— 高階函數

Kotlin知識概括(十二) —— 泛型

Kotlin知識概括(十三) —— 註解

Kotlin知識概括(十四) —— 反射

相關文章
相關標籤/搜索