第2章 Kotlin 語(yǔ)法基礎(chǔ)

第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)成如下圖所示

編程語(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ǔ)句(ifwhen)
循環(huán)語(yǔ)句(for、while )
跳轉(zhuǎn)語(yǔ)句 (returnbreak 、continuethrow)

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

breakcontinue都是用來(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)Intmaxv: (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、breakcontinue的跳轉(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ù)的 getset。

調(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)主題。

開(kāi)發(fā)者社區(qū) QRCode.jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容