一、本文概要
本文是對<<Kotlin in Action>>
的學習筆記,如果需要執行相應的程式碼可以訪問線上環境 try.kotlinlang.org,這部分的思維導圖為:
Kotlin
中,我們可以通過 呼叫自己程式碼中定義的函式,來實現 特定語言結構。這些功能與 特定的函式命名 相關,而不是與特定的型別繫結。例如,如果在你的類中定義了一個名為plus
的特殊方法,那麼按照約定,就可以在該類的例項上使用+
運算子,這種技術稱為 約定。
因為由類實現的介面集是固定的,而Kotlin
不能為了實現其他介面而修改現有的類,因此一般 通過擴充套件函式的機制 來為現有的類增添新的 約定方法,從而適應任何現有的Java
類。
二、過載算術運算子
在Kotlin
中,使用約定的最直接的例子就是 算術運算子,在Java
中,全套的算術運算子只能用於基本資料型別,+
運算子可以與String
一起使用。下面,我們看一下在Kotlin
中,如何使用算術運算子來完成一些其它的事情。
2.1 過載二元運算子
假設已經有一個資料類Point
,它包含兩個成員變數,分別是x,y
點的座標值,我們希望通過算術運算子+
對兩個Point
物件相加之後,能夠得到一個新的Point
物件,它的成員變數x,y
為原有兩個Point
物件的x,y
之和。
Point
類定義了一個擴充套件函式plus
,這樣當我們呼叫first + second
,實際上執行的是first.plus(second)
方法來得到一個新的Point
物件。這裡需要注意的是:用於過載運算子的所有函式都需要 用 operator 關鍵字來標記,用來表示你打算 把這個函式作為相應的約定的實現。
所有可過載的二元算術運算子如下,自定義型別的運算子,基本上和標準數字型別的運算子有著相同的優先順序。
a * b
:times
a / b
:div
a % b
:mod
a + b
:plus
a - b
:minus
運算子函式和 Java
- 當從
Java
呼叫Kotlin
運算子非常容易,只需要像普通函式一樣呼叫即可,例如上面的plus
方法。 - 當從
Kotlin
呼叫Java
的時候,對於與Kotlin
約定匹配的函式(不要求使用operator
修飾符,但是引數需要匹配名稱和數量)都可以使用運算子語言來呼叫。如果Java
類定義了一個滿足需求的函式,但是起了一個不同的名稱,可以通過定義一個擴充套件函式來修正這個函式名用來替代現有的Java
方法。
沒有用於位運算的特殊運算子
Kotlin
沒有為標準數字型別Int
,Long
等定義任何位運算子,因此也不允許你為自定型別定義它們。相反,它使用中綴呼叫語法的函式,可以為自定義型別定義相似的函式,下面我們為Point
新增一個and
,用於執行位運算。
operator
關鍵字來宣告,而是用infix
來定義一箇中綴呼叫語法的函式,其它執行位運算的函式包括:shl
、shr
、ushr
、and
、or
、xor
和inv
。
2.2 過載複合賦值運算子
當在定義像plus
這樣的函式,Kotlin
不止支援+
號運算,也支援像+=
這樣的 複合賦值運算子。
first
要宣告為var
。在一些情況下,定義+=
運算子可以 修改使用它的變數所引用的物件,但不會重新分配引用,將一個元素新增到可變集合,就是一個很好的例子:
如果你定義了一個返回值為Unit
,名為plusAssign
的函式,Kotlin
將會在用到+=
運算子的地方使用它,其它二元運算子也有命名相似的對應函式:minusAssign
、timesAssign
等。
當在程式碼中用到+=
的時候,理論上plus
和plusAssign
都可能會被呼叫,如果兩個函式都有定義並且適用,那麼編譯器就會報錯,例如下面這樣的定義:
- 使用 不可變 val 代替可變 var 來修飾
first
,這樣plus
運算子就不再適用。 - 不要同時為一個類新增
plus
和plusAssign
運算。如果一個類是 不可變的,那就應該只提供返回一個新值的運算;如果一個類是 可變的,例如構建器,那麼只需要提供plusAssign
和類似的運算子就夠了。
Kotlin
的標準庫支援集合的這兩種方法:
+
和-
運算子總是返回一個新的集合+=
和-=
運算子用於可變集合時,始終在一個地方修改它們;而它們用於只讀集合時,會返回一個修改過的副本。
作為它們的運算數,可以使用單個元素,也可以使用元素型別一致的其它集合:
執行結果為:2.3 過載一元運算子
過載一元運算的過程和前面看到的方式相同:用預先定義的一個名稱來宣告函式,並用修飾符operator
標記。下面的例子中過載了-a
運算子:
+a
:unaryPlus
-a
:unaryMinus
!a
:not
++a/a++
:inc
--a/a--
:dec
當你定義inc
和dec
函式來過載自增和自減的運算子時,編譯器自動支援與普通數字型別的字首、字尾自增運算子相同的語義。例如字尾運算會先返回變數的值,然後才執行++
操作。
三、過載比較運算子
與算術運算子一樣,在Kotlin
中,可以對任何物件使用比較運算子(==
、!=
、>
和<
),而不僅僅限於基本資料型別。
3.1 等號運算子,equals
如果在Kotlin
中使用==/!=
運算子,它將被轉換成equals
方法的呼叫,和其他運算子不同的是,==
和!=
可以用於可空運算數,比較a == b
會檢查a
是否為飛空,如果不是就呼叫a.equals(b)
,完整的呼叫如下所示:
a?.equals(b) ?: (b == null)
複製程式碼
對於data
修飾的資料類,equals
的實現將會由編譯器自動生成,如果需要手動實現,可以參考下面的做法:
- 比較是否指向同一物件的引用,如果是,那麼直接返回
true
- 型別如果不同,直接返回
false
- 比較作為判斷依據的欄位
equals
函式之所以被標記為override
,這是因為這個方法的實現是在Any
類中定義的,而operator
關鍵字在基本方法中已經標記了。同時,equals
不能實現為擴充套件函式,因為繼承自Any
類的實現始終優先於擴充套件函式。
3.2 排序運算子 compareTo
在Kotlin
中,對於實現了Comparable
介面中定義的compareTo
方法的類可以按約定呼叫,比較運算子<、>、<=、>=
的使用將被轉換為compareTo
,compareTo
的返回型別必須為int
,也就是說p1 < p2
表示式等價於p1.compareTo(p2) < 0
。
下面,我們定義一個Person
類,讓其根據年齡來比較大小:
Kotlin
標準庫函式中的compareValuesBy
函式來簡潔地實現compareTo
方法,這個函式 接收用來計算比較值的一系列回撥,按順序依次呼叫回撥方法,兩兩一組分別做比較:
- 如果值不同,則返回比較結果
- 如果相同,則繼續呼叫下一個
- 如果沒有更多的回撥來呼叫,則返回
0
這些回撥函式可以像lambda
一樣傳遞,或者像這裡做的一樣,作為屬性引用傳遞。
四、集合與區間的約定
處理集合最常見的操作包含兩種:
- 通過下標來獲取和設定元素,使用語法
a[b]
,稱為 下標運算子。 - 檢查元素是否屬於當前集合,使用
in
運算子。
4.1 通過下標來訪問元素:get 和 set
在Kotlin
中,下標運算子是一種約定,使用下標運算子讀取元素會被轉換為get
運算子方法的呼叫,並且寫入元素將呼叫set
,下面我們為Point
類新增類似的方法:
get
的引數可以是任何型別,而不止是Int
,例如,當你對map
使用下標運算子時,引數型別是鍵的型別,它可以是任意型別。還可以定義具有多個引數的get
方法,例如如果要實現一個類來表示二維陣列或矩陣,你可以定義一個方法,例如operator fun get(rowIndex : Int, colIndex : Int)
,然後用matrix[row, col]
來呼叫。
下面,我們再來看一下set
的約定方法:
set
函式後,就可以在賦值語句中使用下標運算子,set
的最後一個引數用來接收賦值語句中(等號)右邊的值,其他引數作為方括號內的下標。
4.2 in 的約定
集合支援的另一個運算子是in
運算子,用於檢查某個物件是否屬於集合,相應的函式叫做contains
,下面的例子用於判斷某個點是否處於矩形範圍之內:
4.3 rangeTo 的約定
要建立一個區間時,使用的是..
語法,例如1..10
代表所有從1
到10
的數字,..
運算子是呼叫rangeTo
函式的一個簡潔方法。rangeTo
返回一個區間,你可以為自己的類定義這個運算子,但是,如果該類實現了Comparable
介面,那麼就不需要了,你可以通過Kotlin
標準庫建立一個任意可比較元素的區間,這個庫定義了可以用於任何可比較元素的rangeTo
函式
operator fun <T : Comparable<T>> T.rangeTo(that : T) : ClosedRange<T>
複製程式碼
這個函式返回一個區間ClosedRanged
,可以用來檢測其它一些元素是否屬於它。
作為例子,我們用LocalData
來構建一個日期的區間:
now..now.plusDays(10)
將會被編譯器轉換為now.rangeTo(now.plusDays(10))
,它並不是LocalDate
的成員函式,而是Comparable
的一個擴充套件函式。
4.4 在 "for" 迴圈中使用 "iterator" 的約定
在for
迴圈中使用in
運算子表示 執行迭代操作,諸如for(x in list) { }
將被轉換成list.iterator()
的呼叫,然後在上面重複呼叫hasNext
和next
方法。
object
來實現匿名內部類的知識。
五、解構宣告和元件函式
解構宣告的功能允許你展開單個複合值,並使用它來初始化多個單獨的變數。它再次用到了約定的原理,要在解構宣告中初始化每個變數,將呼叫名為componentN
的函式,其中N
是宣告中變數的位置。
對於資料類,編譯器為每個在主構造方法中宣告的屬性生成一個componentN
函式,下面的例子顯示瞭如何手動為非資料類宣告這些功能:
解構宣告不僅可以用作函式中的頂層語句,還可以用在其他可以宣告變數的地方,例如使用in
迴圈來列舉map
中的條目:
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:www.jianshu.com/p/fd82d1899…
- 個人主頁:lizejun.cn
- 個人知識總結目錄:lizejun.cn/categories/