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中調用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運算符的能力,但仍是要聽從擴展函數不能訪問private
或 protected
修飾的屬性或方法的特性。
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
方法,直接調用方法。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() |
當定義inc
和dec
函數來重載自增和自減運算符時,編譯器會自動支持與普通數字類型的前綴和後綴自增運算符相同的語義。例如,調用前綴形式 ++a
,其步驟是:
與算術運算符同樣,Kotlin容許對任意類型重載比較運算符(==、!=、>、<等)。能夠直接使用運算符進行比較,不用像Java調用equals
或compareTo
函數。
若是在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;
}
}
複製代碼
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運算符用於檢查某個對象是否屬於集合。它是一種約定,相應的函數爲contains
。
表達式 | 轉換 |
---|---|
a in c | c.contains(a) |
當須要建立區間時,都是使用..運算符。..運算符是調用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)
複製代碼
(0..10).filter {
it % 2 == 0
}.map {
it * it
}.forEach {
println(it)
}
複製代碼
for
循環中可使用in
運算符來表示執行迭代。這意味着Kotlin的for循環將被轉換成list.iterator()
的調用,而後反覆調用hasNext
和 next
方法。
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()
會根據主構造函數中聲明的全部屬性生成一個哈希值;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
中 entry
的鍵和值,快速遍歷。
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 類做爲返回值,來實現返回兩個變量。
和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")
複製代碼
中綴函數必須知足如下要求:
使用..運算符建立的區間是一個閉區間,當咱們須要建立倒序區間或者半閉區間,甚至是設置區間步長時,所使用到的downTo
、until
和step
其實都不是關鍵字,而是一個個使用infix 關鍵字
修飾的方法,只是使用中輟調用來進行呈現。
在建立map時,對key和vlaue使用中輟調用來添加元素,提升可讀性。
val map = mapOf("one" to 1,"two" to 2)
複製代碼
中綴函數調用的優先級低於算術操做符、類型轉換以及 rangeTo 操做符。因此0 until n * 2
與 0 until (n * 2)
等價。
但中綴函數調用的優先級高於布爾操做符&& 與 ||、is 與 in 檢測以及其餘一些操做符。因此7 in 0 until 10
與 7 in (0 until 10)
等價。