第2章 Kotlin 語法基礎

第2章 Kotlin 語法基礎

人與人之間經過語言來交流溝通,互相協做。人與計算機之間怎樣「交流溝通」呢?答案是編程語言。一門語言有詞、短語、句子、文章等,對應到編程語言中就是關鍵字、標識符、表達式、源代碼文件等。一般一門編程語言的基本構成以下圖所示java

編程語言的基本構成

本章咱們學習 Kotlin語言的基礎語法。es6

2.1 變量和標識符

變量(數據名稱)標識一個對象的地址,咱們稱之爲標識符。而具體存放的數據佔用內存的大小和存放的形式則由其類型來決定。編程

在Kotlin中, 全部的變量類型都是引用類型。Kotlin的變量分爲 val (不可變的) 和var (可變的) 。能夠簡單理解爲:數組

val 是隻讀的,僅能一次賦值,後面就不能被從新賦值。
var 是可寫的,在它生命週期中能夠被屢次賦值;安全

使用關鍵字 val 聲明不可變變量jvm

>>> val a:Int = 1
>>> a
1

另外,咱們能夠省略後面的類型Int,直接聲明以下編程語言

>>> val a = 1 // 根據值 1 編譯器可以自動推斷出 `Int` 類型
>>> a
1

用val聲明的變量不能從新賦值ide

>>> val a = 1
>>> a++
error: val cannot be reassigned
a++
^

使用 var 聲明可變變量函數

>>> var b = 1
>>> b = b + 1
>>> b
2

只要可能,儘可能在Kotlin中首選使用val不變值。由於事實上在程序中大部分地方只須要使用不可變的變量。使用val變量能夠帶來可預測的行爲和線程安全等優勢。學習

變量名就是標識符。標識符是由字母、數字、下劃線組成的字符序列,不能以數字開頭。下面是合法的變量名

>>> val _x = 1
>>> val y = 2
>>> val ip_addr = "127.0.0.1"
>>> _x
1
>>> y
2
>>> ip_addr
127.0.0.1

跟Java同樣,變量名區分大小寫。命名遵循駝峯式規範。

2.2 關鍵字與修飾符

一般狀況下,編程語言中都有一些具備特殊意義的標識符是不能用做變量名的,這些具有特殊意義的標識符叫作關鍵字(又稱保留字),編譯器須要針對這些關鍵字進行詞法分析,這是編譯器對源碼進行編譯的基礎步驟之一。

Kotlin中的修飾符關鍵字主要分爲:

類修飾符、訪問修飾符、型變修飾符、成員修飾符、參數修飾符、類型修飾符、函數修飾符、屬性修飾符等。這些修飾符以下表2-1所示

表2-1 Kotlin中的修飾符

類修飾符 說明
abstract 抽象類
final 不可被繼承final類
enum 枚舉類
open 可繼承open類
annotation 註解類
sealed 密封類
data 數據類
成員修飾符 說明
override 重寫函數(方法)
open 聲明函數可被重寫
final 聲明函數不可被重寫
abstract 聲明函數爲抽象函數
lateinit 延遲初始化

|訪問權限控制修飾符|說明
|---|----|
|private|私有,僅當前類可訪問
|protected| 當前類以及繼承該類的可訪問
|public|默認值,對外可訪問
|internal| 整個模塊內可訪問(模塊是指一塊兒編譯的一組 Kotlin 源代碼文件。例如,一個 Maven 工程, 或 Gradle 工程,經過 Ant 任務的一次調用編譯的一組文件等)

協變逆變修飾符 說明
in 消費者類型修飾符,out T 等價於 ? extends T
out 生產者類型修飾符,in T 等價於 ? super T

|函數修飾符| 說明
|---|---|
|tailrec| 尾遞歸
|operator| 運算符重載函數
|infix|中綴函數。例如,給Int定義擴展中綴函數 infix fun Int.shl(x: Int): Int
|inline| 內聯函數
|external|外部函數
|suspend | 掛起協程函數

|屬性修飾符| 說明
|---|---|
|const|常量修飾符

|參數修飾符| 說明
|---|---|
|vararg| 變長參數修飾符
|noinline| 不內聯參數修飾符,有時,只須要將內聯函數的部分參數使用內聯Lambda,其餘的參數不須要內聯,可使用「noinline」關鍵字修飾。例如:inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit)
|crossinline| 當內聯函數不是直接在函數體中使用lambda參數,而是經過其餘執行上下文。這種狀況下能夠在參數前使用「crossinline」關鍵字修飾標識。代碼實例以下。

crossinline代碼實例:

inline fun f(crossinline body: () -> Unit) {
    val f = object: Runnable {
        override fun run() = body()
    }
}

|類型修飾符| 說明
|---|---|
| reified |具體化類型參數 |

除了上面的修飾符關鍵字以外,還有一些其餘特殊語義的關鍵字以下表2-2所示

表2-2 Kotlin中的關鍵字

關鍵字 說明
package 包聲明
as 類型轉換
typealias 類型別名
class 聲明類
this 當前對象引用
super 父類對象引用
val 聲明不可變變量
var 聲明可變變量
fun 聲明函數
for for 循環
null 特殊值 null
true 真值
false 假值
is 類型判斷
throw 拋出異常
return 返回值
break 跳出循環體
continue 繼續下一次循環
object 單例類聲明
if 邏輯判斷if
else 邏輯判斷, 結合if使用
while while 循環
do do 循環
when 條件判斷
interface 接口聲明
file 文件
field 成員
property 屬性
receiver 接收者
param 參數
setparam 設置參數
delegate 委託
import 導入包
where where條件
by 委託類或屬性
get get函數
set set 函數
constructor 構造函數
init 初始化代碼塊
try 異常捕獲
catch 異常捕獲,結合try使用
finally 異常最終執行代碼塊
dynamic 動態的
typeof 類型定義,預留用

這些關鍵字定義在源碼 org.jetbrains.kotlin.lexer.KtTokens.java 中。

2.3 流程控制語句

流程控制語句是編程語言中的核心之一。可分爲:

分支語句(ifwhen)
循環語句(forwhile )
跳轉語句 (returnbreakcontinuethrow)

2.3.1 if表達式

if-else語句是控制程序流程的最基本的形式,其中else是可選的。

在 Kotlin 中,if 是一個表達式,即它會返回一個值(跟Scala同樣)。

代碼示例:

package com.easy.kotlin

fun main(args: Array<String>) {
    println(max(1, 2))
}

fun max(a: Int, b: Int): Int {
    // 表達式返回值
    val max = if (a > b) a else b
    return max
}

另外,if 的分支能夠是代碼塊,最後的表達式做爲該塊的值:

fun max3(a: Int, b: Int): Int {
    val max = if (a > b) {
        print("Max is a")
        a // 最後的表達式做爲該代碼塊的值
    } else {
        print("Max is b")
        b // 同上
    }
    return max
}

if做爲代碼塊時,最後一行爲其返回值。

另外,在Kotlin中沒有相似true? 1: 0這樣的三元表達式。對應的寫法是使用if else語句:

if(true) 1 else 0

if-else語句規則:

  • if後的括號不能省略,括號裏表達式的值須是布爾型。

代碼反例:

>>> 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)
   ^
  • 若是條件體內只有一條語句須要執行,那麼if後面的大括號能夠省略。良好的編程風格建議加上大括號。
>>> if(true) println(1) else println(0)
1
>>> if(true) { println(1)}  else{ println(0)}
1

編程實例:用 if - else 語句判斷某年份是不是閏年。

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表達式

when表達式相似於 switch-case 表達式。when會對全部的分支進行檢查直到有一個條件知足。但相比switch而言,when語句要更加的強大,靈活。

Kotlin的極簡語法表達風格,使得咱們對分支檢查的代碼寫起來更加簡單直接:

fun casesWhen(obj: Any?) {
    when (obj) {
        0,1,2,3,4,5,6,7,8,9 -> println("${obj} ===> 這是一個0-9之間的數字")
        "hello" -> println("${obj} ===> 這個是字符串hello")
        is Char -> println("${obj} ===> 這是一個 Char 類型數據")
        else -> println("${obj} ===> else相似於Java中的 case-switch 中的 default")
    }
}

fun main(args: Array<String>) {
    casesWhen(1)
    casesWhen("hello")
    casesWhen('X')
    casesWhen(null)
}

輸出

1 ===> 這是一個0-9之間的數字
hello ===> 這個是字符串hello
X ===> 這是一個 Char 類型數據
null ===> else相似於Java中的 case-switch 中的 default

像 if 同樣,when 的每個分支也能夠是一個代碼塊,它的值是塊中最後的表達式的值。

若是其餘分支都不知足條件會到 else 分支(相似default)。

若是咱們有不少分支須要用相同的方式處理,則能夠把多個分支條件放在一塊兒,用逗號分隔:

0,1,2,3,4,5,6,7,8,9 -> println("${obj} ===> 這是一個0-9之間的數字")

咱們能夠用任意表達式(而不僅是常量)做爲分支條件

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 -> { // 注意這個塊
            print("x is neither 1 nor 2")
        }
    }
}

咱們也能夠檢測一個值在 in 或者不在 !in 一個區間或者集合中:

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")
    }

編程實例: 用when語句寫一個階乘函數。

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循環

for 循環能夠對任何提供迭代器(iterator)的對象進行遍歷,語法以下:

for (item in collection) {
    print(item)
}

若是想要經過索引遍歷一個數組或者一個 list,能夠這麼作:

for (i in array.indices) {
    print(array[i])
}

或者使用庫函數 withIndex

for ((index, value) in array.withIndex()) {
    println("the element at $index is $value")
}

編程實例: 編寫一個 Kotlin 程序在屏幕上輸出1!+2!+3!+……+10!的和。

咱們使用上面的fact函數,代碼實現以下

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循環

while 和 do .. while使用方式跟C、Java語言基本一致。

代碼示例

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都是用來控制循環結構的,主要是用來中止循環(中斷跳轉),可是有區別,下面咱們分別介紹。

break

break用於徹底結束一個循環,直接跳出循環體,而後執行循環後面的語句。

問題場景:

打印數字1-10,只要遇到偶數就結束打印。

代碼示例:

for (i in 1..10) {
        println(i)
        if (i % 2 == 0) {
            break
        }
    } // break to here

輸出:

1
2

continue

continue是隻終止本輪循環,可是還會繼續下一輪循環。能夠簡單理解爲,直接在當前語句處中斷,跳轉到循環入口,執行下一輪循環。而break則是徹底終止循環,跳轉到循環出口。

問題場景:

打印1-10中的奇數。

代碼示例:

for (i in 1..10) {
        if (i % 2 == 0) {
            continue
        }
        println(i)
    }

輸出

1
3
5
7
9

2.3.6 return返回

在Java、C語言中,return語句使咱們再常見不過的了。雖然在Scala,Groovy這樣的語言中,函數的返回值能夠不須要顯示用return來指定,可是咱們仍然認爲,使用return的編碼風格更加容易閱讀理解 (尤爲是在分支流代碼塊中)。

在Kotlin中,除了表達式的值,有返回值的函數都要求顯式使用return來返回其值。

代碼示例

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不能省略
}

咱們在Kotlin中,能夠直接使用=符號來直接返回一個函數的值,這樣的函數咱們稱爲函數字面量。

代碼示例

>>> 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

後面的函數體語句有沒有大括號 {} 意思徹底不一樣。加了大括號,意義就徹底不同了。

>>> 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

咱們再經過下面的代碼示例清晰的看出:

>>> 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

能夠看出,sumfmaxf的返回值是函數類型:

() -> kotlin.Int
() -> kotlin.Int

這點跟Scala是不一樣的。在Scala中,帶不帶大括號{},意思同樣:

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在大括號的使用上,是徹底不一樣的。

而後,調用函數方式是直接調用invoke()函數:sumf(1,1).invoke()。

kotlin 中 return 語句會從最近的函數或匿名函數中返回,可是在Lambda表達式中遇到return,則直接返回最近的外層函數。例以下面兩個函數是不一樣的:

val intArray = intArrayOf(1, 2, 3, 4, 5)
    intArray.forEach {
        if (it == 3) return // 在Lambda表達式中的return 直接返回最近的外層函數
        println(it)
    }

輸出:

1
2

遇到 3 時會直接返回(有點相似循環體中的break行爲)。

而咱們給forEach傳入一個匿名函數 fun(a: Int) ,這個匿名函數裏面的return不會跳出forEach循環,有點像continue的邏輯:

val intArray = intArrayOf(1, 2, 3, 4, 5)
    intArray.forEach(fun(a: Int) { 
        if (a == 3) return // 從最近的函數中返回
        println(a)
    })

輸出

1
2
4
5

爲了顯式的指明 return 返回的地址,kotlin 還提供了 @Label (標籤) 來控制返回語句,且看下節分解。

2.3.7 標籤(label)

在 Kotlin 中任何表達式均可以用標籤(label)來標記。 標籤的格式爲標識符後跟 @ 符號,例如:abc@_isOK@ 都是有效的標籤。咱們能夠用Label標籤來控制 returnbreakcontinue的跳轉(jump)行爲。

代碼示例:

val intArray = intArrayOf(1, 2, 3, 4, 5)
    intArray.forEach here@ {
        if (it == 3) return@here // 指令跳轉到 lambda 表達式標籤 here@ 處。繼續下一個it=4的遍歷循環
        println(it)
    }

輸出:

1
2
4
5

咱們在 lambda 表達式開頭處添加了標籤here@ ,咱們能夠這麼理解:該標籤至關因而記錄了Lambda表達式的指令執行入口地址, 而後在表達式內部咱們使用return@here 來跳轉至Lambda表達式該地址處。這樣代碼更加易懂。

另外,咱們也可使用隱式標籤更方便。 該標籤與接收該 lambda 的函數同名。

代碼示例

val intArray = intArrayOf(1, 2, 3, 4, 5)
    intArray.forEach {
        if (it == 3) return@forEach // 返回到 @forEach 處繼續下一個循環
        println(it)
    }

輸出:

1
2
4
5

接收該Lambda表達式的函數是forEach, 因此咱們能夠直接使用 return@forEach ,來跳轉到此處執行下一輪循環。

2.3.8 throw表達式

在 Kotlin 中 throw 是表達式,它的類型是特殊類型 Nothing。 該類型沒有值。跟C、Java中的void 意思同樣。

>>> Nothing::class
class java.lang.Void

咱們在代碼中,用 Nothing 來標記無返回的函數:

>>> fun fail(msg:String):Nothing{ throw IllegalArgumentException(msg) }
>>> fail("XXXX")
java.lang.IllegalArgumentException: XXXX
    at Line57.fail(Unknown Source)

另外,若是把一個throw表達式的值賦值給一個變量,須要顯式聲明類型爲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

另外,由於ex變量是Nothing類型,沒有任何值,因此沒法當作參數傳給函數。

2.4 操做符與重載

Kotlin 容許咱們爲本身的類型提供預約義的一組操做符的實現。這些操做符具備固定的符號表示(如 +*)和固定的優先級。這些操做符的符號定義以下:

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 操做符優先級

Kotlin中操做符的優先級(Precedence)以下表所示

表2-3 操做符的優先級

優先級 標題 符號
最高 後綴(Postfix ) ++, --, ., ?., ?
前綴(Prefix) -, +, ++, --, !, labelDefinition@
右手類型運算(Type RHS,right-hand side class type (RHS) ) :, as, as?
乘除取餘(Multiplicative) *, /, %
加減(Additive ) +, -
區間範圍(Range) ..
Infix函數 例如,給 Int 定義擴展 infix fun Int.shl(x: Int): Int {...},這樣調用 1 shl 2,等同於1.shl(2)
Elvis操做符 ?:
命名檢查符(Named checks) in, !in, is, !is
比較大小(Comparison) <, >, <=, >=
相等性判斷(Equality) ==, !=, ===, !==
與 (Conjunction) &&
或 (Disjunction) `\ \ `
最低 賦值(Assignment) =, +=, -=, *=, /=, %=

爲實現這些的操做符,Kotlin爲二元操做符左側的類型和一元操做符的參數類型,提供了相應的函數或擴展函數。重載操做符的函數須要用 operator 修飾符標記。中綴操做符的函數使用infix修飾符標記。

2.4.2 一元操做符

一元操做符(unary operation) 有前綴操做符、遞增和遞減操做符等。

前綴操做符

前綴操做符放在操做數的前面。它們分別如表2-4所示

表2-4 前綴操做符

表達式 翻譯爲
+a a.unaryPlus()
-a a.unaryMinus()
!a a.not()

如下是重載一元減運算符的示例:

package com.easy.kotlin

data class Point(val x: Int, val y: Int)
operator fun Point.unaryMinus() = Point(-x, -y)

測試代碼:

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 遞增和遞減操做符

表達式 翻譯爲
a++ a.inc() 返回值是a
a-- a.dec() 返回值是a
++a a.inc() 返回值是a+1
--a a.dec() 返回值是a-1

inc()dec() 函數必須返回一個值,它用於賦值給使用
++-- 操做的變量。

2.4.3 二元操做符

Kotlin中的二元操做符有算術運算符、索引訪問操做符、調用操做符、計算並賦值操做符、相等與不等操做符、Elvis 操做符、比較操做符、中綴操做符等。下面咱們分別做介紹。

算術運算符

表2-6 算術運算符

表達式 翻譯爲
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

字符串的+運算符重載

先用代碼舉個例子:

>>> ""+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,寫這樣的Kotlin代碼就顯得不大友好),只能顯式調用toString來相加:

>>> 1.toString()+""
1

自定義重載的 + 運算符

下面咱們使用一個計數類 Counter 重載的 + 運算符來增長index的計數值。

代碼示例

data class Counter(var index: Int)

operator fun Counter.plus(increment: Int): Counter {
    return Counter(index + increment)
}

測試類

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操做符

表達式 翻譯爲
a in b b.contains(a)
a !in b !b.contains(a)

in操做符等價於函數contains 。

索引訪問操做符

表2-8 索引訪問操做符操做符

表達式 翻譯爲
a[i] a.get(i)
a[i] = b a.set(i, b)

方括號轉換爲調用帶有適當數量參數的 getset

調用操做符

表2-9 調用操做符

表達式 翻譯爲
a() a.invoke()
a(i) a.invoke(i)

圓括號轉換爲調用帶有適當數量參數的 invoke

計算並賦值操做符

表2-10 計算並賦值操做符

表達式 翻譯爲
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)

對於賦值操做,例如 a += b,編譯器會試着生成 a = a + b 的代碼(這裏包含類型檢查:a + b 的類型必須是 a 的子類型)。

相等與不等操做符

Kotlin 中有兩種類型的相等性:

  • 引用相等 === !==(兩個引用指向同一對象)
  • 結構相等 == !=( 使用equals() 判斷)

表2-11 相等與不等操做符

表達式 翻譯爲
a == b a?.equals(b) ?: (b === null)
a != b !(a?.equals(b) ?: (b === null))

這個 == 操做符有些特殊:它被翻譯成一個複雜的表達式,用於篩選 null 值。

意思是:若是 a 不是 null 則調用 equals(Any?) 函數並返回其值;不然(即 a === null)就計算 b === null 的值並返回。

當與 null 顯式比較時,a == null 會被自動轉換爲 a=== null

注意===!==不可重載。

Elvis 操做符 ?:

在Kotin中,Elvis操做符特定是跟null比較。也就是說

y = x?:0

等價於

val y = if(x!==null) x else 0

主要用來做null安全性檢查。

Elvis操做符 ?: 是一個二元運算符,若是第一個操做數爲真,則返回第一個操做數,不然將計算並返回其第二個操做數。它是三元條件運算符的變體。命名靈感來自貓王的髮型風格。

Kotlin中沒有這樣的三元運算符 true?1:0,取而代之的是if(true) 1 else 0。而Elvis操做符算是精簡版的三元運算符。

咱們在Java中使用的三元運算符的語法,你一般要重複變量兩次, 示例:

String name = "Elvis Presley";
String displayName = (name != null) ? name : "Unknown";

取而代之,你可使用Elvis操做符

String name = "Elvis Presley";
String displayName = name?:"Unknown"

咱們能夠看出,用Elvis操做符(?:)能夠把帶有默認值的if/else結構寫的及其短小。用Elvis操做符不用檢查null(避免了NullPointerException),也不用重複變量。

這個Elvis操做符功能在Spring 表達式語言 (SpEL)中提供。

在Kotlin中固然就沒有理由不支持這個特性。

代碼示例:

>>> 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 比較操做符

表達式 翻譯爲
a > b a.compareTo(b) > 0
a < b a.compareTo(b) < 0
a >= b a.compareTo(b) >= 0
a <= b a.compareTo(b) <= 0

全部的比較都轉換爲對 compareTo 的調用,這個函數須要返回 Int

用infix函數自定義中綴操做符

咱們能夠經過自定義infix函數來實現中綴操做符。

代碼示例

data class Person(val name: String, val age: Int)

infix fun Person.grow(years: Int): Person {
    return Person(name, age + years)
}

測試代碼

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 包聲明

咱們在*.kt源文件開頭聲明package命名空間。例如在PackageDemo.kt源代碼中,咱們按照以下方式聲明包

package com.easy.kotlin

fun what(){ // 包級函數
    println("This is WHAT ?")
}

fun main(args:Array<String>){ // 一個包下面只能有一個main函數
    println("Hello,World!")
}

class Motorbike{ // 包裏面的類
    fun drive(){
        println("Drive The Motorbike ...")
    }
}

Kotlin中的目錄與包的結構無需匹配,源代碼文件能夠在文件系統中的任意位置。

若是一個測試類PackageDemoTest跟PackageDemo在同一個包下面,咱們就不須要單獨去import 類和包級函數,能夠在代碼裏直接調用

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() 函數跟 PackageDemoTest類在同一個包命名空間下,能夠直接調用,不須要 importMotorbike類跟 PackageDemoTest類同理分析。

若是不在同一個package下面,咱們就須要import對應的類和函數。例如,咱們在 src/test/kotlin目錄下新建一個package com.easy.kotlin.test, 使用package com.easy.kotlin 下面的類和函數,示例以下

package com.easy.kotlin.test

import com.easy.kotlin.Motorbike // 導入類Motorbike
import com.easy.kotlin.what // 導入包級函數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會會默認導入一些基礎包到每一個 Kotlin 文件中:

kotlin.*
kotlin.annotation.*
kotlin.collections.*
kotlin.comparisons.* (自 1.1 起)
kotlin.io.*
kotlin.ranges.*
kotlin.sequences.*
kotlin.text.*

根據目標平臺還會導入額外的包:

JVM:

java.lang.*
kotlin.jvm.*

JS:

kotlin.js.*

本章小結

相關文章
相關標籤/搜索