前序
??????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)
}
??????擴(kuò)展函數(shù)可以很好的對現(xiàn)有的Java類添加Kotlin運(yùn)算符的能力,但還是要遵從擴(kuò)展函數(shù)不能訪問private 或 protected修飾的屬性或方法的特性。
復(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)定義inc和dec函數(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)用equals或compareTo函數(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)用hasNext 和 next 方法。
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()
數(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)聲明快速獲取map 中 entry 的鍵和值,快速遍歷。
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)
}
??????否則,你想要的值在主構(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)
}
中輟調(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ū)間步長時,所使用到的downTo、until和step 其實都不是關(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 * 2與 0 until (n * 2)等價。
??????但中綴函數(shù)調(diào)用的優(yōu)先級高于布爾操作符&& 與 ||、is 與 in 檢測以及其他一些操作符。所以7 in 0 until 10與 7 in (0 until 10)等價。
參考資料:
- 《Kotlin實戰(zhàn)》
- Kotlin官網(wǎng)
android Kotlin系列:
Kotlin知識歸納(二) —— 讓函數(shù)更好調(diào)用