第2章 Kotlin 語(yǔ)法基礎(chǔ)
人與人之間通過(guò)語(yǔ)言來(lái)交流溝通,互相協(xié)作。人與計(jì)算機(jī)之間怎樣“交流溝通”呢?答案是編程語(yǔ)言。一門語(yǔ)言有詞、短語(yǔ)、句子、文章等,對(duì)應(yīng)到編程語(yǔ)言中就是關(guān)鍵字、標(biāo)識(shí)符、表達(dá)式、源代碼文件等。通常一門編程語(yǔ)言的基本構(gòu)成如下圖所示

本章我們學(xué)習(xí) Kotlin語(yǔ)言的基礎(chǔ)語(yǔ)法。
2.1 變量和標(biāo)識(shí)符
變量(數(shù)據(jù)名稱)標(biāo)識(shí)一個(gè)對(duì)象的地址,我們稱之為標(biāo)識(shí)符。而具體存放的數(shù)據(jù)占用內(nèi)存的大小和存放的形式則由其類型來(lái)決定。
在Kotlin中, 所有的變量類型都是引用類型。Kotlin的變量分為 val (不可變的) 和var (可變的) 。可以簡(jiǎn)單理解為:
val是只讀的,僅能一次賦值,后面就不能被重新賦值。
var是可寫(xiě)的,在它生命周期中可以被多次賦值;
使用關(guān)鍵字 val 聲明不可變變量
>>> val a:Int = 1
>>> a
1
另外,我們可以省略后面的類型Int,直接聲明如下
>>> val a = 1 // 根據(jù)值 1 編譯器能夠自動(dòng)推斷出 `Int` 類型
>>> a
1
用val聲明的變量不能重新賦值
>>> val a = 1
>>> a++
error: val cannot be reassigned
a++
^
使用 var 聲明可變變量
>>> var b = 1
>>> b = b + 1
>>> b
2
只要可能,盡量在Kotlin中首選使用val不變值。因?yàn)槭聦?shí)上在程序中大部分地方只需要使用不可變的變量。使用val變量可以帶來(lái)可預(yù)測(cè)的行為和線程安全等優(yōu)點(diǎn)。
變量名就是標(biāo)識(shí)符。標(biāo)識(shí)符是由字母、數(shù)字、下劃線組成的字符序列,不能以數(shù)字開(kāi)頭。下面是合法的變量名
>>> val _x = 1
>>> val y = 2
>>> val ip_addr = "127.0.0.1"
>>> _x
1
>>> y
2
>>> ip_addr
127.0.0.1
跟Java一樣,變量名區(qū)分大小寫(xiě)。命名遵循駝峰式規(guī)范。
2.2 關(guān)鍵字與修飾符
通常情況下,編程語(yǔ)言中都有一些具有特殊意義的標(biāo)識(shí)符是不能用作變量名的,這些具備特殊意義的標(biāo)識(shí)符叫做關(guān)鍵字(又稱保留字),編譯器需要針對(duì)這些關(guān)鍵字進(jìn)行詞法分析,這是編譯器對(duì)源碼進(jìn)行編譯的基礎(chǔ)步驟之一。
Kotlin中的修飾符關(guān)鍵字主要分為:
類修飾符、訪問(wèn)修飾符、型變修飾符、成員修飾符、參數(shù)修飾符、類型修飾符、函數(shù)修飾符、屬性修飾符等。這些修飾符如下表2-1所示
表2-1 Kotlin中的修飾符
類修飾符
| 類修飾符 | 說(shuō)明 |
|---|---|
| abstract | 抽象類 |
| final | 不可被繼承final類 |
| enum | 枚舉類 |
| open | 可繼承open類 |
| annotation | 注解類 |
| sealed | 密封類 |
| data | 數(shù)據(jù)類 |
成員修飾符
| 成員修飾符 | 說(shuō)明 |
|---|---|
| override | 重寫(xiě)函數(shù)(方法) |
| open | 聲明函數(shù)可被重寫(xiě) |
| final | 聲明函數(shù)不可被重寫(xiě) |
| abstract | 聲明函數(shù)為抽象函數(shù) |
| lateinit | 延遲初始化 |
訪問(wèn)權(quán)限修飾符
| 訪問(wèn)權(quán)限修飾符 | 說(shuō)明 |
|---|---|
| private | 私有,僅當(dāng)前類可訪問(wèn) |
| protected | 當(dāng)前類以及繼承該類的可訪問(wèn) |
| public | 默認(rèn)值,對(duì)外可訪問(wèn) |
| internal | 整個(gè)模塊內(nèi)可訪問(wèn)(模塊是指一起編譯的一組 Kotlin 源代碼文件。例如,一個(gè) Maven 工程, 或 Gradle 工程,通過(guò) Ant 任務(wù)的一次調(diào)用編譯的一組文件等) |
協(xié)變逆變修飾符
| 協(xié)變逆變修飾符 | 說(shuō)明 |
|---|---|
| in | 消費(fèi)者類型修飾符,out T 等價(jià)于 ? extends T |
| out | 生產(chǎn)者類型修飾符,in T 等價(jià)于 ? super T |
函數(shù)修飾符
| 函數(shù)修飾符 | 說(shuō)明 |
|---|---|
| tailrec | 尾遞歸 |
| operator | 運(yùn)算符重載函數(shù) |
| infix | 中綴函數(shù)。例如,給Int定義擴(kuò)展中綴函數(shù) infix fun Int.shl(x: Int): Int |
| inline | 內(nèi)聯(lián)函數(shù) |
| external | 外部函數(shù) |
| suspend | 掛起協(xié)程函數(shù) |
屬性修飾符
| 屬性修飾符 | 說(shuō)明 |
|---|---|
| const | 常量修飾符 |
參數(shù)修飾符
| 參數(shù)修飾符 | 說(shuō)明 |
|---|---|
| vararg | 變長(zhǎng)參數(shù)修飾符 |
| noinline | 不內(nèi)聯(lián)參數(shù)修飾符,有時(shí),只需要將內(nèi)聯(lián)函數(shù)的部分參數(shù)使用內(nèi)聯(lián)Lambda,其他的參數(shù)不需要內(nèi)聯(lián),可以使用“noinline”關(guān)鍵字修飾。例如:inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit)
|
| crossinline | 當(dāng)內(nèi)聯(lián)函數(shù)不是直接在函數(shù)體中使用lambda參數(shù),而是通過(guò)其他執(zhí)行上下文。這種情況下可以在參數(shù)前使用“crossinline”關(guān)鍵字修飾標(biāo)識(shí)。 |
代碼實(shí)例如下。
crossinline代碼實(shí)例:
inline fun f(crossinline body: () -> Unit) {
val f = object: Runnable {
override fun run() = body()
}
}
| 類型修飾符 | 說(shuō)明 |
|---|---|
| reified | 具體化類型參數(shù) |
除了上面的修飾符關(guān)鍵字之外,還有一些其他特殊語(yǔ)義的關(guān)鍵字如下表2-2所示
表2-2 Kotlin中的關(guān)鍵字
| 關(guān)鍵字 | 說(shuō)明 |
|---|---|
| package | 包聲明 |
| as | 類型轉(zhuǎn)換 |
| typealias | 類型別名 |
| class | 聲明類 |
| this | 當(dāng)前對(duì)象引用 |
| super | 父類對(duì)象引用 |
| val | 聲明不可變變量 |
| var | 聲明可變變量 |
| fun | 聲明函數(shù) |
| for | for 循環(huán) |
| null | 特殊值 null |
| true | 真值 |
| false | 假值 |
| is | 類型判斷 |
| throw | 拋出異常 |
| return | 返回值 |
| break | 跳出循環(huán)體 |
| continue | 繼續(xù)下一次循環(huán) |
| object | 單例類聲明 |
| if | 邏輯判斷if |
| else | 邏輯判斷, 結(jié)合if使用 |
| while | while 循環(huán) |
| do | do 循環(huán) |
| when | 條件判斷 |
| interface | 接口聲明 |
| file | 文件 |
| field | 成員 |
| property | 屬性 |
| receiver | 接收者 |
| param | 參數(shù) |
| setparam | 設(shè)置參數(shù) |
| delegate | 委托 |
| import | 導(dǎo)入包 |
| where | where條件 |
| by | 委托類或?qū)傩?/td> |
| get | get函數(shù) |
| set | set 函數(shù) |
| constructor | 構(gòu)造函數(shù) |
| init | 初始化代碼塊 |
| try | 異常捕獲 |
| catch | 異常捕獲,結(jié)合try使用 |
| finally | 異常最終執(zhí)行代碼塊 |
| dynamic | 動(dòng)態(tài)的 |
| typeof | 類型定義,預(yù)留用 |
這些關(guān)鍵字定義在源碼 org.jetbrains.kotlin.lexer.KtTokens.java 中。
2.3 流程控制語(yǔ)句
流程控制語(yǔ)句是編程語(yǔ)言中的核心之一??煞譃椋?/p>
分支語(yǔ)句(
if、when)
循環(huán)語(yǔ)句(for、while)
跳轉(zhuǎn)語(yǔ)句 (return、break、continue、throw)
2.3.1 if表達(dá)式
if-else語(yǔ)句是控制程序流程的最基本的形式,其中else是可選的。
在 Kotlin 中,if 是一個(gè)表達(dá)式,即它會(huì)返回一個(gè)值(跟Scala一樣)。
代碼示例:
package com.easy.kotlin
fun main(args: Array<String>) {
println(max(1, 2))
}
fun max(a: Int, b: Int): Int {
// 表達(dá)式返回值
val max = if (a > b) a else b
return max
}
另外,if 的分支可以是代碼塊,最后的表達(dá)式作為該塊的值:
fun max3(a: Int, b: Int): Int {
val max = if (a > b) {
print("Max is a")
a // 最后的表達(dá)式作為該代碼塊的值
} else {
print("Max is b")
b // 同上
}
return max
}
if作為代碼塊時(shí),最后一行為其返回值。
另外,在Kotlin中沒(méi)有類似true? 1: 0這樣的三元表達(dá)式。對(duì)應(yīng)的寫(xiě)法是使用if else語(yǔ)句:
if(true) 1 else 0
if-else語(yǔ)句規(guī)則:
- if后的括號(hào)不能省略,括號(hào)里表達(dá)式的值須是布爾型。
代碼反例:
>>> if("a") 1
error: type mismatch: inferred type is String but Boolean was expected
if("a") 1
^
>>> if(1) println(1)
error: the integer literal does not conform to the expected type Boolean
if(1)
^
- 如果條件體內(nèi)只有一條語(yǔ)句需要執(zhí)行,那么if后面的大括號(hào)可以省略。良好的編程風(fēng)格建議加上大括號(hào)。
>>> if(true) println(1) else println(0)
1
>>> if(true) { println(1)} else{ println(0)}
1
編程實(shí)例:用 if - else 語(yǔ)句判斷某年份是否是閏年。
fun isLeapYear(year: Int): Boolean {
var isLeapYear: Boolean
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
isLeapYear = true
} else {
isLeapYear = false
}
return isLeapYear
}
fun main(args: Array<String>) {
println(isLeapYear(2017)) // false
println(isLeapYear(2020)) // true
}
2.3.2 when表達(dá)式
when表達(dá)式類似于 switch-case 表達(dá)式。when會(huì)對(duì)所有的分支進(jìn)行檢查直到有一個(gè)條件滿足。但相比switch而言,when語(yǔ)句要更加的強(qiáng)大,靈活。
Kotlin的極簡(jiǎn)語(yǔ)法表達(dá)風(fēng)格,使得我們對(duì)分支檢查的代碼寫(xiě)起來(lái)更加簡(jiǎn)單直接:
fun casesWhen(obj: Any?) {
when (obj) {
0,1,2,3,4,5,6,7,8,9 -> println("${obj} ===> 這是一個(gè)0-9之間的數(shù)字")
"hello" -> println("${obj} ===> 這個(gè)是字符串hello")
is Char -> println("${obj} ===> 這是一個(gè) Char 類型數(shù)據(jù)")
else -> println("${obj} ===> else類似于Java中的 case-switch 中的 default")
}
}
fun main(args: Array<String>) {
casesWhen(1)
casesWhen("hello")
casesWhen('X')
casesWhen(null)
}
輸出
1 ===> 這是一個(gè)0-9之間的數(shù)字
hello ===> 這個(gè)是字符串hello
X ===> 這是一個(gè) Char 類型數(shù)據(jù)
null ===> else類似于Java中的 case-switch 中的 default
像 if 一樣,when 的每一個(gè)分支也可以是一個(gè)代碼塊,它的值是塊中最后的表達(dá)式的值。
如果其他分支都不滿足條件會(huì)到 else 分支(類似default)。
如果我們有很多分支需要用相同的方式處理,則可以把多個(gè)分支條件放在一起,用逗號(hào)分隔:
0,1,2,3,4,5,6,7,8,9 -> println("${obj} ===> 這是一個(gè)0-9之間的數(shù)字")
我們可以用任意表達(dá)式(而不只是常量)作為分支條件
fun switch(x: Int) {
val s = "123"
when (x) {
-1, 0 -> print("x == -1 or x == 0")
1 -> print("x == 1")
2 -> print("x == 2")
8 -> print("x is 8")
parseInt(s) -> println("x is 123")
else -> { // 注意這個(gè)塊
print("x is neither 1 nor 2")
}
}
}
我們也可以檢測(cè)一個(gè)值在 in 或者不在 !in 一個(gè)區(qū)間或者集合中:
val x = 1
val validNumbers = arrayOf(1, 2, 3)
when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}
編程實(shí)例: 用when語(yǔ)句寫(xiě)一個(gè)階乘函數(shù)。
fun fact(n: Int): Int {
var result = 1
when (n) {
0, 1 -> result = 1
else -> result = n * fact(n - 1)
}
return result
}
fact(10) // 3628800
2.3.3 for循環(huán)
for 循環(huán)可以對(duì)任何提供迭代器(iterator)的對(duì)象進(jìn)行遍歷,語(yǔ)法如下:
for (item in collection) {
print(item)
}
如果想要通過(guò)索引遍歷一個(gè)數(shù)組或者一個(gè) list,可以這么做:
for (i in array.indices) {
print(array[i])
}
或者使用庫(kù)函數(shù) withIndex:
for ((index, value) in array.withIndex()) {
println("the element at $index is $value")
}
另外,范圍(Ranges)表達(dá)式也可用于循環(huán)當(dāng)中:
if (i in 1..10) { // 等同于1 <= i && i <= 10
println(i)
}
簡(jiǎn)寫(xiě)
(1..10).forEach { print(it) }
其中的操作符形式的 1..10 等價(jià)于 1.rangeTo(10) 函數(shù)調(diào)用 ,由in和!in進(jìn)行連接。
編程實(shí)例: 編寫(xiě)一個(gè) Kotlin 程序在屏幕上輸出1!+2!+3!+……+10!的和。
我們使用上面的fact函數(shù),代碼實(shí)現(xiàn)如下
fun sumFact(n: Int): Int {
var sum = 0
for (i in 1..n) {
sum += fact(i)
}
return sum
}
sumFact(10) // 4037913
2.3.4 while循環(huán)
while 和 do .. while使用方式跟C、Java語(yǔ)言基本一致。
代碼示例
package com.easy.kotlin
fun main(args: Array<String>) {
var x = 10
while (x > 0) {
x--
println(x)
}
var y = 10
do {
y = y + 1
println(y)
} while (y < 20) // y的作用域包含此處
}
2.3.5 break 和 continue
break和continue都是用來(lái)控制循環(huán)結(jié)構(gòu)的,主要是用來(lái)停止循環(huán)(中斷跳轉(zhuǎn)),但是有區(qū)別,下面我們分別介紹。
break
break用于完全結(jié)束一個(gè)循環(huán),直接跳出循環(huán)體,然后執(zhí)行循環(huán)后面的語(yǔ)句。
問(wèn)題場(chǎng)景:
打印數(shù)字1-10,只要遇到偶數(shù)就結(jié)束打印。
代碼示例:
for (i in 1..10) {
println(i)
if (i % 2 == 0) {
break
}
} // break to here
輸出:
1
2
continue
continue是只終止本輪循環(huán),但是還會(huì)繼續(xù)下一輪循環(huán)??梢院?jiǎn)單理解為,直接在當(dāng)前語(yǔ)句處中斷,跳轉(zhuǎn)到循環(huán)入口,執(zhí)行下一輪循環(huán)。而break則是完全終止循環(huán),跳轉(zhuǎn)到循環(huán)出口。
問(wèn)題場(chǎng)景:
打印1-10中的奇數(shù)。
代碼示例:
for (i in 1..10) {
if (i % 2 == 0) {
continue
}
println(i)
}
輸出
1
3
5
7
9
2.3.6 return返回
在Java、C語(yǔ)言中,return語(yǔ)句使我們?cè)俪R?jiàn)不過(guò)的了。雖然在Scala,Groovy這樣的語(yǔ)言中,函數(shù)的返回值可以不需要顯示用return來(lái)指定,但是我們?nèi)匀徽J(rèn)為,使用return的編碼風(fēng)格更加容易閱讀理解 (尤其是在分支流代碼塊中)。
在Kotlin中,除了表達(dá)式的值,有返回值的函數(shù)都要求顯式使用return來(lái)返回其值。
代碼示例
fun sum(a: Int,b: Int): Int{
return a+b // 這里的return不能省略
}
fun max(a: Int, b: Int): Int {
if (a > b){
return a // return不能省略
} else{
return b // return不能省略
}
我們?cè)贙otlin中,可以直接使用=符號(hào)來(lái)直接返回一個(gè)函數(shù)的值,這樣的函數(shù)我們稱為函數(shù)字面量。
代碼示例
>>> fun sum(a: Int,b: Int) = a + b
>>> fun max(a: Int, b: Int) = if (a > b) a else b
>>> sum(1,10)
11
>>> max(1,2)
2
>>> val sum=fun(a:Int, b:Int) = a+b
>>> sum
(kotlin.Int, kotlin.Int) -> kotlin.Int
>>> sum(1,1)
2
后面的函數(shù)體語(yǔ)句有沒(méi)有大括號(hào) {} 意思完全不同。加了大括號(hào),意義就完全不一樣了。
>>> val sumf = fun(a:Int, b:Int) = {a+b}
>>> sumf
(kotlin.Int, kotlin.Int) -> () -> kotlin.Int
>>> sumf(1,1)
() -> kotlin.Int
>>> sumf(1,1).invoke()
2
我們?cè)偻ㄟ^(guò)下面的代碼示例清晰的看出:
>>> fun sumf(a:Int,b:Int) = {a+b}
>>> sumf(1,1)
() -> kotlin.Int
>>> sumf(1,1).invoke()
2
>>> fun maxf(a:Int, b:Int) = {if(a>b) a else b}
>>> maxf(1,2)
() -> kotlin.Int
>>> maxf(1,2).invoke()
2
可以看出,sumf,maxf的返回值是函數(shù)類型:
() -> kotlin.Int
() -> kotlin.Int
這點(diǎn)跟Scala是不同的。在Scala中,帶不帶大括號(hào){},意思一樣:
scala> def maxf(x:Int, y:Int) = { if(x>y) x else y }
maxf: (x: Int, y: Int)Int
scala> def maxv(x:Int, y:Int) = if(x>y) x else y
maxv: (x: Int, y: Int)Int
scala> maxf(1,2)
res4: Int = 2
scala> maxv(1,2)
res6: Int = 2
我們可以看出maxf: (x: Int, y: Int)Int跟maxv: (x: Int, y: Int)Int簽名是一樣的。在這里,Kotlin跟Scala在大括號(hào)的使用上,是完全不同的。
然后,調(diào)用函數(shù)方式是直接調(diào)用invoke()函數(shù):sumf(1,1).invoke()。
kotlin 中 return 語(yǔ)句會(huì)從最近的函數(shù)或匿名函數(shù)中返回,但是在Lambda表達(dá)式中遇到return,則直接返回最近的外層函數(shù)。例如下面兩個(gè)函數(shù)是不同的:
val intArray = intArrayOf(1, 2, 3, 4, 5)
intArray.forEach {
if (it == 3) return // 在Lambda表達(dá)式中的return 直接返回最近的外層函數(shù)
println(it)
}
輸出:
1
2
遇到 3 時(shí)會(huì)直接返回(有點(diǎn)類似循環(huán)體中的break行為)。
而我們給forEach傳入一個(gè)匿名函數(shù) fun(a: Int) ,這個(gè)匿名函數(shù)里面的return不會(huì)跳出forEach循環(huán),有點(diǎn)像continue的邏輯:
val intArray = intArrayOf(1, 2, 3, 4, 5)
intArray.forEach(fun(a: Int) {
if (a == 3) return // 從最近的函數(shù)中返回
println(a)
})
輸出
1
2
4
5
為了顯式的指明 return 返回的地址,kotlin 還提供了 @Label (標(biāo)簽) 來(lái)控制返回語(yǔ)句,且看下節(jié)分解。
2.3.7 標(biāo)簽(label)
在 Kotlin 中任何表達(dá)式都可以用標(biāo)簽(label)來(lái)標(biāo)記。 標(biāo)簽的格式為標(biāo)識(shí)符后跟 @ 符號(hào),例如:abc@、_isOK@ 都是有效的標(biāo)簽。我們可以用Label標(biāo)簽來(lái)控制 return、break 或 continue的跳轉(zhuǎn)(jump)行為。
代碼示例:
val intArray = intArrayOf(1, 2, 3, 4, 5)
intArray.forEach here@ {
if (it == 3) return@here // 指令跳轉(zhuǎn)到 lambda 表達(dá)式標(biāo)簽 here@ 處。繼續(xù)下一個(gè)it=4的遍歷循環(huán)
println(it)
}
輸出:
1
2
4
5
我們?cè)?lambda 表達(dá)式開(kāi)頭處添加了標(biāo)簽here@ ,我們可以這么理解:該標(biāo)簽相當(dāng)于是記錄了Lambda表達(dá)式的指令執(zhí)行入口地址, 然后在表達(dá)式內(nèi)部我們使用return@here 來(lái)跳轉(zhuǎn)至Lambda表達(dá)式該地址處。這樣代碼更加易懂。
另外,我們也可以使用隱式標(biāo)簽更方便。 該標(biāo)簽與接收該 lambda 的函數(shù)同名。
代碼示例
val intArray = intArrayOf(1, 2, 3, 4, 5)
intArray.forEach {
if (it == 3) return@forEach // 返回到 @forEach 處繼續(xù)下一個(gè)循環(huán)
println(it)
}
輸出:
1
2
4
5
接收該Lambda表達(dá)式的函數(shù)是forEach, 所以我們可以直接使用 return@forEach ,來(lái)跳轉(zhuǎn)到此處執(zhí)行下一輪循環(huán)。
2.3.8 throw表達(dá)式
在 Kotlin 中 throw 是表達(dá)式,它的類型是特殊類型 Nothing。 該類型沒(méi)有值。跟C、Java中的void 意思一樣。
>>> Nothing::class
class java.lang.Void
我們?cè)诖a中,用 Nothing 來(lái)標(biāo)記無(wú)返回的函數(shù):
>>> fun fail(msg:String):Nothing{ throw IllegalArgumentException(msg) }
>>> fail("XXXX")
java.lang.IllegalArgumentException: XXXX
at Line57.fail(Unknown Source)
另外,如果把一個(gè)throw表達(dá)式的值賦值給一個(gè)變量,需要顯式聲明類型為Nothing , 代碼示例如下
>>> val ex = throw Exception("YYYYYYYY")
error: 'Nothing' property type needs to be specified explicitly
val ex = throw Exception("YYYYYYYY")
^
>>> val ex:Nothing = throw Exception("YYYYYYYY")
java.lang.Exception: YYYYYYYY
另外,因?yàn)閑x變量是Nothing類型,沒(méi)有任何值,所以無(wú)法當(dāng)做參數(shù)傳給函數(shù)。
2.4 操作符與重載
Kotlin 允許我們?yōu)樽约旱念愋吞峁╊A(yù)定義的一組操作符的實(shí)現(xiàn)。這些操作符具有固定的符號(hào)表示(如 + 或 *)和固定的優(yōu)先級(jí)。這些操作符的符號(hào)定義如下:
KtSingleValueToken LBRACKET = new KtSingleValueToken("LBRACKET", "[");
KtSingleValueToken RBRACKET = new KtSingleValueToken("RBRACKET", "]");
KtSingleValueToken LBRACE = new KtSingleValueToken("LBRACE", "{");
KtSingleValueToken RBRACE = new KtSingleValueToken("RBRACE", "}");
KtSingleValueToken LPAR = new KtSingleValueToken("LPAR", "(");
KtSingleValueToken RPAR = new KtSingleValueToken("RPAR", ")");
KtSingleValueToken DOT = new KtSingleValueToken("DOT", ".");
KtSingleValueToken PLUSPLUS = new KtSingleValueToken("PLUSPLUS", "++");
KtSingleValueToken MINUSMINUS = new KtSingleValueToken("MINUSMINUS", "--");
KtSingleValueToken MUL = new KtSingleValueToken("MUL", "*");
KtSingleValueToken PLUS = new KtSingleValueToken("PLUS", "+");
KtSingleValueToken MINUS = new KtSingleValueToken("MINUS", "-");
KtSingleValueToken EXCL = new KtSingleValueToken("EXCL", "!");
KtSingleValueToken DIV = new KtSingleValueToken("DIV", "/");
KtSingleValueToken PERC = new KtSingleValueToken("PERC", "%");
KtSingleValueToken LT = new KtSingleValueToken("LT", "<");
KtSingleValueToken GT = new KtSingleValueToken("GT", ">");
KtSingleValueToken LTEQ = new KtSingleValueToken("LTEQ", "<=");
KtSingleValueToken GTEQ = new KtSingleValueToken("GTEQ", ">=");
KtSingleValueToken EQEQEQ = new KtSingleValueToken("EQEQEQ", "===");
KtSingleValueToken ARROW = new KtSingleValueToken("ARROW", "->");
KtSingleValueToken DOUBLE_ARROW = new KtSingleValueToken("DOUBLE_ARROW", "=>");
KtSingleValueToken EXCLEQEQEQ = new KtSingleValueToken("EXCLEQEQEQ", "!==");
KtSingleValueToken EQEQ = new KtSingleValueToken("EQEQ", "==");
KtSingleValueToken EXCLEQ = new KtSingleValueToken("EXCLEQ", "!=");
KtSingleValueToken EXCLEXCL = new KtSingleValueToken("EXCLEXCL", "!!");
KtSingleValueToken ANDAND = new KtSingleValueToken("ANDAND", "&&");
KtSingleValueToken OROR = new KtSingleValueToken("OROR", "||");
KtSingleValueToken SAFE_ACCESS = new KtSingleValueToken("SAFE_ACCESS", "?.");
KtSingleValueToken ELVIS = new KtSingleValueToken("ELVIS", "?:");
KtSingleValueToken QUEST = new KtSingleValueToken("QUEST", "?");
KtSingleValueToken COLONCOLON = new KtSingleValueToken("COLONCOLON", "::");
KtSingleValueToken COLON = new KtSingleValueToken("COLON", ":");
KtSingleValueToken SEMICOLON = new KtSingleValueToken("SEMICOLON", ";");
KtSingleValueToken DOUBLE_SEMICOLON = new KtSingleValueToken("DOUBLE_SEMICOLON", ";;");
KtSingleValueToken RANGE = new KtSingleValueToken("RANGE", "..");
KtSingleValueToken EQ = new KtSingleValueToken("EQ", "=");
KtSingleValueToken MULTEQ = new KtSingleValueToken("MULTEQ", "*=");
KtSingleValueToken DIVEQ = new KtSingleValueToken("DIVEQ", "/=");
KtSingleValueToken PERCEQ = new KtSingleValueToken("PERCEQ", "%=");
KtSingleValueToken PLUSEQ = new KtSingleValueToken("PLUSEQ", "+=");
KtSingleValueToken MINUSEQ = new KtSingleValueToken("MINUSEQ", "-=");
KtKeywordToken NOT_IN = KtKeywordToken.keyword("NOT_IN", "!in");
KtKeywordToken NOT_IS = KtKeywordToken.keyword("NOT_IS", "!is");
KtSingleValueToken HASH = new KtSingleValueToken("HASH", "#");
KtSingleValueToken AT = new KtSingleValueToken("AT", "@");
KtSingleValueToken COMMA = new KtSingleValueToken("COMMA", ",");
2.4.1 操作符優(yōu)先級(jí)
Kotlin中操作符的優(yōu)先級(jí)(Precedence)如下表所示
表2-3 操作符的優(yōu)先級(jí)
| 優(yōu)先級(jí) | 標(biāo)題 | 符號(hào) |
|---|---|---|
| 最高 | 后綴(Postfix ) |
++, --, ., ?., ?
|
| 前綴(Prefix) |
-, +, ++, --, !, labelDefinition@
|
|
| 右手類型運(yùn)算(Type RHS,right-hand side class type (RHS) ) |
:, as, as?
|
|
| 乘除取余(Multiplicative) |
*, /, %
|
|
| 加減(Additive ) |
+, -
|
|
| 區(qū)間范圍(Range) | .. |
|
| Infix函數(shù) | 例如,給Int定義擴(kuò)展 infix fun Int.shl(x: Int): Int {...},這樣調(diào)用 1 shl 2,等同于1.shl(2)
|
|
| Elvis操作符 | ?: |
|
| 命名檢查符(Named checks) |
in, !in, is, !is
|
|
| 比較大?。–omparison) |
<, >, <=, >=
|
|
| 相等性判斷(Equality) |
==, !=, ===, !==
|
|
| 與 (Conjunction) | && |
|
| 或 (Disjunction) | || |
|
| 最低 | 賦值(Assignment) |
=, +=, -=, *=, /=, %=
|
為實(shí)現(xiàn)這些的操作符,Kotlin為二元操作符左側(cè)的類型和一元操作符的參數(shù)類型,提供了相應(yīng)的函數(shù)或擴(kuò)展函數(shù)。重載操作符的函數(shù)需要用 operator 修飾符標(biāo)記。中綴操作符的函數(shù)使用infix修飾符標(biāo)記。
2.4.2 一元操作符
一元操作符(unary operation) 有前綴操作符、遞增和遞減操作符等。
前綴操作符
前綴操作符放在操作數(shù)的前面。它們分別如表2-4所示
表2-4 前綴操作符
| 表達(dá)式 | 翻譯為 |
|---|---|
+a |
a.unaryPlus() |
-a |
a.unaryMinus() |
!a |
a.not() |
以下是重載一元減運(yùn)算符的示例:
package com.easy.kotlin
data class Point(val x: Int, val y: Int)
operator fun Point.unaryMinus() = Point(-x, -y)
測(cè)試代碼:
package com.easy.kotlin
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@RunWith(JUnit4::class)
class OperatorDemoTest {
@Test
fun testPointUnaryMinus() {
val p = Point(1, 1)
val np = -p
println(np) //Point(x=-1, y=-1)
}
}
遞增和遞減操作符
表2-5 遞增和遞減操作符
| 表達(dá)式 | 翻譯為 |
|---|---|
a++ |
a.inc() 返回值是a
|
a-- |
a.dec() 返回值是a
|
++a |
a.inc() 返回值是a+1
|
--a |
a.dec() 返回值是a-1
|
inc() 和 dec() 函數(shù)必須返回一個(gè)值,它用于賦值給使用
++ 或 -- 操作的變量。
2.4.3 二元操作符
Kotlin中的二元操作符有算術(shù)運(yùn)算符、索引訪問(wèn)操作符、調(diào)用操作符、計(jì)算并賦值操作符、相等與不等操作符、Elvis 操作符、比較操作符、中綴操作符等。下面我們分別作介紹。
算術(shù)運(yùn)算符
表2-6 算術(shù)運(yùn)算符
| 表達(dá)式 | 翻譯為 |
|---|---|
a + b |
a.plus(b) |
a - b |
a.minus(b) |
a * b |
a.times(b) |
a / b |
a.div(b) |
a % b |
a.rem(b)、 a.mod(b)
|
a..b |
a.rangeTo(b) |
代碼示例
>>> val a=10
>>> val b=3
>>> a+b
13
>>> a-b
7
>>> a/b
3
>>> a%b
1
>>> a..b
10..3
>>> b..a
3..10
字符串的+運(yùn)算符重載
先用代碼舉個(gè)例子:
>>> ""+1
1
>>> 1+""
error: none of the following functions can be called with the arguments supplied:
public final operator fun plus(other: Byte): Int defined in kotlin.Int
public final operator fun plus(other: Double): Double defined in kotlin.Int
public final operator fun plus(other: Float): Float defined in kotlin.Int
public final operator fun plus(other: Int): Int defined in kotlin.Int
public final operator fun plus(other: Long): Long defined in kotlin.Int
public final operator fun plus(other: Short): Int defined in kotlin.Int
1+""
^
從上面的示例,我們可以看出,在Kotlin中1+""是不允許的(這地方,相比Scala,寫(xiě)這樣的Kotlin代碼就顯得不大友好),只能顯式調(diào)用toString來(lái)相加:
>>> 1.toString()+""
1
自定義重載的 + 運(yùn)算符
下面我們使用一個(gè)計(jì)數(shù)類 Counter 重載的 + 運(yùn)算符來(lái)增加index的計(jì)數(shù)值。
代碼示例
data class Counter(var index: Int)
operator fun Counter.plus(increment: Int): Counter {
return Counter(index + increment)
}
測(cè)試類
package com.easy.kotlin
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@RunWith(JUnit4::class)
class OperatorDemoTest
@Test
fun testCounterIndexPlus() {
val c = Counter(1)
val cplus = c + 10
println(cplus) //Counter(index=11)
}
}
in操作符
表2-7 in操作符
| 表達(dá)式 | 翻譯為 |
|---|---|
a in b |
b.contains(a) |
a !in b |
!b.contains(a) |
in操作符等價(jià)于函數(shù)contains 。
索引訪問(wèn)操作符
表2-8 索引訪問(wèn)操作符操作符
| 表達(dá)式 | 翻譯為 |
|---|---|
a[i] |
a.get(i) |
a[i] = b |
a.set(i, b) |
方括號(hào)轉(zhuǎn)換為調(diào)用帶有適當(dāng)數(shù)量參數(shù)的 get 和 set。
調(diào)用操作符
表2-9 調(diào)用操作符
| 表達(dá)式 | 翻譯為 |
|---|---|
a() |
a.invoke() |
a(i) |
a.invoke(i) |
圓括號(hào)轉(zhuǎn)換為調(diào)用帶有適當(dāng)數(shù)量參數(shù)的 invoke。
計(jì)算并賦值操作符
表2-10 計(jì)算并賦值操作符
| 表達(dá)式 | 翻譯為 |
|---|---|
a += b |
a.plusAssign(b) |
a -= b |
a.minusAssign(b) |
a *= b |
a.timesAssign(b) |
a /= b |
a.divAssign(b) |
a %= b |
a.modAssign(b) |
對(duì)于賦值操作,例如 a += b,編譯器會(huì)試著生成 a = a + b 的代碼(這里包含類型檢查:a + b 的類型必須是 a 的子類型)。
相等與不等操作符
Kotlin 中有兩種類型的相等性:
- 引用相等
===!==(兩個(gè)引用指向同一對(duì)象) - 結(jié)構(gòu)相等
==!=( 使用equals()判斷)
表2-11 相等與不等操作符
| 表達(dá)式 | 翻譯為 |
|---|---|
a == b |
a?.equals(b) ?: (b === null) |
a != b |
!(a?.equals(b) ?: (b === null)) |
這個(gè) == 操作符有些特殊:它被翻譯成一個(gè)復(fù)雜的表達(dá)式,用于篩選 null 值。
意思是:如果 a 不是 null 則調(diào)用 equals(Any?) 函數(shù)并返回其值;否則(即 a === null)就計(jì)算 b === null 的值并返回。
當(dāng)與 null 顯式比較時(shí),a == null 會(huì)被自動(dòng)轉(zhuǎn)換為 a=== null
注意:=== 和 !==不可重載。
Elvis 操作符 ?:
在Kotin中,Elvis操作符特定是跟null比較。也就是說(shuō)
y = x?:0
等價(jià)于
val y = if(x!==null) x else 0
主要用來(lái)作null安全性檢查。
Elvis操作符 ?: 是一個(gè)二元運(yùn)算符,如果第一個(gè)操作數(shù)為真,則返回第一個(gè)操作數(shù),否則將計(jì)算并返回其第二個(gè)操作數(shù)。它是三元條件運(yùn)算符的變體。命名靈感來(lái)自貓王的發(fā)型風(fēng)格。
Kotlin中沒(méi)有這樣的三元運(yùn)算符 true?1:0,取而代之的是if(true) 1 else 0。而Elvis操作符算是精簡(jiǎn)版的三元運(yùn)算符。
我們?cè)贘ava中使用的三元運(yùn)算符的語(yǔ)法,你通常要重復(fù)變量?jī)纱危?示例:
String name = "Elvis Presley";
String displayName = (name != null) ? name : "Unknown";
取而代之,你可以使用Elvis操作符
String name = "Elvis Presley";
String displayName = name?:"Unknown"
我們可以看出,用Elvis操作符(?:)可以把帶有默認(rèn)值的if/else結(jié)構(gòu)寫(xiě)的及其短小。用Elvis操作符不用檢查null(避免了NullPointerException),也不用重復(fù)變量。
這個(gè)Elvis操作符功能在Spring 表達(dá)式語(yǔ)言 (SpEL)中提供。
在Kotlin中當(dāng)然就沒(méi)有理由不支持這個(gè)特性。
代碼示例:
>>> val x = null
>>> val y = x?:0
>>> y
0
>>> val x = false
>>> val y = x?:0
>>> y
false
>>> val x = ""
>>> val y = x?:0
>>> y
>>> val x = "abc"
>>> val y = x?:0
>>> y
abc
比較操作符
表2-12 比較操作符
| 表達(dá)式 | 翻譯為 |
|---|---|
a > b |
a.compareTo(b) > 0 |
a < b |
a.compareTo(b) < 0 |
a >= b |
a.compareTo(b) >= 0 |
a <= b |
a.compareTo(b) <= 0 |
所有的比較都轉(zhuǎn)換為對(duì) compareTo 的調(diào)用,這個(gè)函數(shù)需要返回 Int 值
用infix函數(shù)自定義中綴操作符
我們可以通過(guò)自定義infix函數(shù)來(lái)實(shí)現(xiàn)中綴操作符。
代碼示例
data class Person(val name: String, val age: Int)
infix fun Person.grow(years: Int): Person {
return Person(name, age + years)
}
測(cè)試代碼
package com.easy.kotlin
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@RunWith(JUnit4::class)
class InfixFunctionDemoTest {
@Test fun testInfixFuntion() {
val person = Person("Jack", 20)
println(person.grow(2))
println(person grow 2)
}
}
輸出
Person(name=Jack, age=22)
Person(name=Jack, age=22)
2.5 包聲明
我們?cè)?code>*.kt源文件開(kāi)頭聲明package命名空間。例如在PackageDemo.kt源代碼中,我們按照如下方式聲明包
package com.easy.kotlin
fun what(){ // 包級(jí)函數(shù)
println("This is WHAT ?")
}
fun main(args:Array<String>){ // 一個(gè)包下面只能有一個(gè)main函數(shù)
println("Hello,World!")
}
class Motorbike{ // 包里面的類
fun drive(){
println("Drive The Motorbike ...")
}
}
Kotlin中的目錄與包的結(jié)構(gòu)無(wú)需匹配,源代碼文件可以在文件系統(tǒng)中的任意位置。
如果一個(gè)測(cè)試類PackageDemoTest跟PackageDemo在同一個(gè)包下面,我們就不需要單獨(dú)去import 類和包級(jí)函數(shù),可以在代碼里直接調(diào)用
package com.easy.kotlin
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@RunWith(JUnit4::class)
class PackageDemoTest {
@Test
fun testWhat() {
what()
}
@Test
fun testDriveMotorbike(){
val motorbike = Motorbike()
motorbike.drive()
}
}
其中,what() 函數(shù)跟PackageDemoTest類在同一個(gè)包命名空間下,可以直接調(diào)用,不需要 import。Motorbike類跟PackageDemoTest類同理分析。
如果不在同一個(gè)package下面,我們就需要import對(duì)應(yīng)的類和函數(shù)。例如,我們?cè)?src/test/kotlin目錄下新建一個(gè)package com.easy.kotlin.test, 使用package com.easy.kotlin 下面的類和函數(shù),示例如下
package com.easy.kotlin.test
import com.easy.kotlin.Motorbike // 導(dǎo)入類Motorbike
import com.easy.kotlin.what // 導(dǎo)入包級(jí)函數(shù)what
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@RunWith(JUnit4::class)
class PackageDemoTest {
@Test
fun testWhat() {
what()
}
@Test
fun testDriveMotorbike() {
val motorbike = Motorbike()
motorbike.drive()
}
}
Kotlin會(huì)會(huì)默認(rèn)導(dǎo)入一些基礎(chǔ)包到每個(gè) Kotlin 文件中:
kotlin.*
kotlin.annotation.*
kotlin.collections.*
kotlin.comparisons.* (自 1.1 起)
kotlin.io.*
kotlin.ranges.*
kotlin.sequences.*
kotlin.text.*
根據(jù)目標(biāo)平臺(tái)還會(huì)導(dǎo)入額外的包:
JVM:
java.lang.*
kotlin.jvm.*
JS:
kotlin.js.*
本章小結(jié)
Kotlin 開(kāi)發(fā)者社區(qū)
國(guó)內(nèi)第一Kotlin 開(kāi)發(fā)者社區(qū)公眾號(hào),主要分享、交流 Kotlin 編程語(yǔ)言、Spring Boot、Android、React.js/Node.js、函數(shù)式編程、編程思想等相關(guān)主題。
