第3章 Kotlin語言基礎 《Kotlin 極簡教程》

第3章 Kotlin語言基礎


《Kotlin極簡教程》正式上架:

點擊這裏 > 去京東商城購買閱讀

點擊這裏 > 去天貓商城購買閱讀

很是感謝您親愛的讀者,你們請多支持!!!有任何問題,歡迎隨時與我交流~


掌握基礎,持續練習html

學習任何東西,都是一個由表及裏的過程。學習一門編程語言也同樣。對於一門編程語言來講,「表」 就是基本詞彙(關鍵字、標識符等)、句子(表達式)和語法。java

每一門編程語言的學習內容都會涉及: 運行環境、基礎數據類型(數字、字符串、數組、集合、映射字典等) 、表達式、 流程控制 、類、方法(函數)
等等,不一樣的語言會借鑑其餘的語言特性,同時也會有各自的特性。這樣咱們就能夠經過對比學習來加深理解。另外,咱們還經過大量實踐深刻理解,達到熟練使用。python

所謂「紙上得來終覺淺,絕知此事要躬行」是也。下面讓咱們開始吧。git

3.1 包(package)

咱們先來舉個例子。好比說,程序員A寫了一個類叫 JSON , 程序員B也寫了一個類叫 JSON。而後,咱們在寫代碼的時候,想要同時使用這兩個類,該怎麼區分呢?程序員

一個答案是使用目錄命名空間。對應在Java中,就是使用package來組織類,以確保類名的惟一性。上面說的例子,A寫的類放到package com.abc.fastjson 中, B寫的類就放到 package com.bbc.jackjson中。這樣咱們在代碼中,就能夠根據命名空間來分別使用這兩個類。調用示例以下es6

com.abc.fastjson.JSON.toJSONString()
com.bbc.jackjson.JSON.parseJSONObject()

在Kotlin中也沿襲了Java的 package 這個概念,同時作了一些擴展。github

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

package com.easy.kotlin

/**
 * Created by jack on 2017/6/8.
 */

fun what(){
    println("This is WHAT ?")
}

class Motorbike{
    fun drive(){
        println("Drive The Motorbike ...")
    }
}

fun main(args:Array<String>){
    println("Hello,World!")
}

包的聲明處於源文件頂部。這裏,咱們聲明瞭包 com.easy.kotlin , 裏面定義了包級函數 what() , 同時定義了一個類 Motorbike 。另外,目錄與包的結構無需匹配:源代碼能夠在文件系統的任意位置。編程

咱們怎麼使用這些類和函數呢?咱們寫一個Junit 測試類來示例說明。json

首先,咱們使用標準Gradle工程目錄,對應的測試代碼放在test目錄下。具體目錄結構以下

螢幕快照 2017-06-08 22.54.12.png

咱們在測試源代碼目錄 src/test/kotlin下面新建一個包,跟src/main/kotlin在同一個 package com.easy.kotlin。而後,在此包下面新建一個測試類PackageDemoTest

package com.easy.kotlin

import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

/**
 * Created by jack on 2017/6/8.
 */

@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
import com.easy.kotlin.what
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4


/**
 * Created by jack on 2017/6/8.
 */

@RunWith(JUnit4::class)
class PackageDemoTest {

    @Test
    fun testWhat() {
        what()
    }

    @Test
    fun testDriveMotorbike() {
        val motorbike = Motorbike()
        motorbike.drive()
    }

}

咱們使用import com.easy.kotlin.Motorbike導入類,直接使用import com.easy.kotlin.what導入包級函數。

上面咱們使用JUnit4測試框架。在build.gradle中的依賴是

testCompile group: 'junit', name: 'junit', version: '4.12'

右擊測試類,點擊執行

螢幕快照 2017-06-08 23.10.36.png

運行結果

螢幕快照 2017-06-08 23.10.59.png

另外,若是咱們不定義package命令空間,則默認在根級目錄。例如直接在src/main/kotlin 源代碼目錄下面新建 DefaultPackageDemo.kt 類

import java.util.*

/**
 * Created by jack on 2017/6/8.
 */

fun now() {
    println("Now Date is: " + Date())
}

class Car{
    fun drive(){
        println("Drive The Car ... ")
    }

}

若是,咱們一樣在src/test/kotlin 目錄下面新建測試類DefaultPackageDemoTest

import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

/**
 * Created by jack on 2017/6/8.
 */


@RunWith(JUnit4::class)
class DefaultPackageDemoTest {

    @Test
    fun testDefaultPackage() {
        now()
        val car = Car()
        car.drive()
    }


}

咱們不須要import now() 函數和 Car類,能夠直接調用。若是咱們在 src/test/kotlin/com/easy/kotlin/PackageDemoTest.kt 測試類裏面調用now() 函數和 Car類, 咱們按照下面的方式import

import now
import Car

PackageDemoTest.kt完整測試代碼以下

package com.easy.kotlin

import now
import Car
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

/**
 * Created by jack on 2017/6/8.
 */

@RunWith(JUnit4::class)
class PackageDemoTest {

    @Test
    fun testWhat() {
        what()
    }

    @Test
    fun testDriveMotorbike(){
        val motorbike = Motorbike()
        motorbike.drive()
    }

    @Test
    fun testDefaultPackage() {
        now()
        val car = Car()
        car.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.*

本小節示例工程源代碼:https://github.com/EasyKotlin...

3.2 聲明變量和值

首先,在Kotlin中, 一切都是對象。因此,全部變量也都是對象(也就是說,任何變量都是根據引用類型來使用的)。

Kotlin的變量分爲 var (可變的) 和 val (不可變的)。

能夠簡單理解爲:

var 是可寫的,在它生命週期中能夠被屢次賦值;

val 是隻讀的,僅能一次賦值,後面就不能被從新賦值。

代碼示例

package com.easy.kotlin

import java.util.*

/**
 * Created by jack on 2017/6/8.
 */

class VariableVSValue {
    fun declareVar() {
        var a = 1
        a = 2
        println(a)
        println(a::class)
        println(a::class.java)

        var x = 5 // 自動推斷出 `Int` 類型
        x += 1

        println("x = $x")
    }

    fun declareVal() {
        val b = "a"
        //b  = "b" //編譯器會報錯: Val cannot be reassigned
        println(b)
        println(b::class)
        println(b::class.java)

        val c: Int = 1  // 當即賦值
        val d = 2   // 自動推斷出 `Int` 類型
        val e: Int  // 若是沒有初始值類型不能省略
        e = 3       // 明確賦值
        println("c = $c, d = $d, e = $e")
    }
}

咱們知道,在Java中也分可變與不可變(final)。在Kotlin中,更簡潔的、更經常使用的場景是:只要可能,儘可能在Kotlin中首選使用val不變值。由於事實上在程序中大部分地方使用不可變的變量,可帶來不少益處,如:可預測的行爲和線程安全。

3.3 變量類型推斷

3.3.1 省去變量類型

在Kotlin中大部分狀況你不須要說明你使用對象的類型,編譯器能夠直接推斷出它的類型。代碼示例

fun typeInference(){
        val str = "abc"
        println(str)
        println(str is String)
        println(str::class)
        println(str::class.java)

//        abc
//        true
//        class java.lang.String (Kotlin reflection is not available)
//        class java.lang.String

        val d = Date()
        println(d)
        println(d is Date)
        println(d::class)
        println(d::class.java)

//        Fri Jun 09 00:06:33 CST 2017
//        true
//        class java.util.Date (Kotlin reflection is not available)
//        class java.util.Date

        val bool = true
        println(bool)
        println(bool::class)
        println(bool::class.java)

//        true
//        boolean (Kotlin reflection is not available)
//        boolean

        val array = arrayOf(1,2,3)
        println(array)
        println(array is Array)
        println(array::class)
        println(array::class.java)

//        [Ljava.lang.Integer;@7b5eadd8
//        true
//        class [Ljava.lang.Integer; (Kotlin reflection is not available)
//        class [Ljava.lang.Integer;
    }

因此,咱們只須要依據要產生的變量類型填寫var或val,其類型一般可以被推斷出來。編譯器可以檢測到其類型,自動地完成類型轉換。固然,咱們也能夠明確地指定變量類型。

可是,類型推斷不是全部的。例如,整型變量Int不能賦值Long變量。下面的代碼不能經過編譯:

fun Int2Long(){
        val x:Int = 10
        val y:Long = x // Type mismatch
    }

咱們須要顯式地調用對應的類型轉換函數進行轉換:

fun Int2Long(){
        val x:Int = 10
//        val y:Long = x // Type mismatch
        val y: Long = x.toLong()
    }

3.3.2 使用is運算符進行類型檢測

is 運算符檢測一個表達式是否某類型的一個實例。

若是一個不可變的局部變量或屬性已經判斷出爲某類型,那麼檢測後的分支中能夠直接看成該類型使用,無需顯式轉換:

fun getLength(obj: Any): Int? {
        var result = 0
        if (obj is String) {
            // `obj` 在該條件分支內自動轉換成 `String`
            println(obj::class) //class java.lang.String
            result = obj.length
            println(result)
        }
        // 在離開類型檢測分支後,`obj` 仍然是 `Any` 類型
        println(obj::class) // class java.lang.Object
        return result
    }

測試類以下

@Test
    fun testGetLength() {
        val obj = "abcdef"
        val len = variableVSValue.getLength(obj)
        Assert.assertTrue(len == 6)

        val obj2:Any = Any()
        variableVSValue.getLength(obj2)

    }

3.4 字符串與其模板表達式

原始字符串(raw string)由三重引號(""")分隔(這個跟python同樣)。原始字符串能夠包含換行符和任何其餘字符。

package com.easy.kotlin

/**
 * Created by jack on 2017/6/9.
 */


fun main(args: Array<String>) {
    val rawString = """
fun helloWorld(val name : String) {
   println("Hello, world!")
}
"""
    println(rawString)

}

字符串能夠包含模板表達式。模板表達式以美圓符號($)開始。

val fooTemplateString = "$rawString has ${rawString.length} characters"
    println(fooTemplateString)

3.5 流程控制語句

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

分支語句(ifwhen)

循環語句(forwhile )和

跳轉語句 (returnbreakcontinuethrow)等。

3.5.1 if表達式

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

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

代碼示例:

package com.easy.kotlin

/**
 * Created by jack on 2017/6/9.
 */


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 //  return if (a > b) a else b
}

fun max1(a: Int, b: Int): Int {
    // 傳統用法
    var max1 = a
    if (a < b) max1 = b
    return max1

}

fun max2(a: Int, b: Int): Int {

// With else
    var max2: Int
    if (a > b) {
        max2 = a
    } else {
        max2 = b
    }
    return max2
}

另外,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 表達式只有一個分支, 或者分支的結果是 Unit , 它的值就是 Unit 。

示例代碼

>>> val x = if(1==1) true
>>> x
kotlin.Unit
>>> val y = if(1==1) true else false
>>> y
true

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語句是可選的,else if 語句也是可選的。
  • else和else if同時出現時,else必須出如今else if 以後。
  • 若是有多條else if語句同時出現,那麼若是有一條else if語句的表達式測試成功,那麼會忽略掉其餘全部else if和else分支。
  • 若是出現多個if,只有一個else的情形,else子句歸屬於最內層的if語句。

以上規則跟Java、C語言基本相同。

3.5.2 when表達式

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

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

fun cases(obj: Any) {
    when (obj) {
        1 -> print("第一項")
        "hello" -> print("這個是字符串hello")
        is Long -> print("這是一個Long類型數據")
        !is String -> print("這不是String類型的數據")
        else -> print("else相似於Java中的default")
    }
}

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

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

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

fun switch(x: Any) {
    when (x) {
        -1, 0 -> print("x == -1 or x == 0")
        1 -> print("x == 1")
        2 -> print("x == 2")
        else -> { // 注意這個塊
            print("x is neither 1 nor 2")
        }
    }
}

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

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

3.5.3 for循環

Kotlin的for循環跟現代的程序設計語言基本相同。

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

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

循環體能夠是一個代碼塊。

for (i in intArray) {
    ...
}

代碼示例

/**
 * For loop iterates through anything that provides an iterator.
 * See http://kotlinlang.org/docs/reference/control-flow.html#for-loops
 */
fun main(args: Array<String>) {
    for (arg in args)
        println(arg)
    // or
    println()
    for (i in args.indices)
        println(args[i])
}

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

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

或者你能夠用庫函數 withIndex

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

3.5.4 while循環

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

代碼示例

package com.easy.kotlin

/**
 * Created by jack on 2017/6/9.
 */


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的做用域包含此處
}

### 3.5.5 break 和 continue

breakcontinue都是用來控制循環結構的,主要是用來中止循環(中斷跳轉)。

1.break

咱們在寫代碼的時候,常常會遇到在某種條件出現的時候,就直接提早終止循環。而不是等到循環條件爲false時才終止。這個時候,咱們就可使用break結束循環。break用於徹底結束一個循環,直接跳出循環體,而後執行循環後面的語句。

問題場景:

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

代碼示例:

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

測試代碼:

@Test
    fun testBreakDemo_1(){
        breakDemo_1()
    }

輸出:

1
2

2.continue

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

問題場景:

打印數字0~10,可是不打印偶數。

代碼示例:

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

測試代碼

@Test
    fun testContinueDemo() {
        continueDemo()
    }

輸出

1
3
5
7
9

3.5.6 return返回

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

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

代碼示例

fun sum(a: Int,b: Int): Int{
    return a+b
}

fun max(a: Int, b: Int): Int { if (a > b) return a else return b}

咱們在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
error: function invocation 'invoke()' expected
sumf(1,1).invoke
          ^
>>> 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()函數。經過REPL的編譯錯誤提示信息,咱們也能夠看出,在Kotlin中,調用無參函數也是要加上括號()的。

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

fun returnDemo_1() {
    println(" START " + ::returnDemo_1.name)
    val intArray = intArrayOf(1, 2, 3, 4, 5)
    intArray.forEach {
        if (it == 3) return
        println(it)
    }
    println(" END " + ::returnDemo_2.name)
}

//1
//2

fun returnDemo_2() {
    println(" START " + ::returnDemo_2.name)
    val intArray = intArrayOf(1, 2, 3, 4, 5)
    intArray.forEach(fun(a: Int) {
        if (a == 3) return
        println(a)
    })
    println(" END " + ::returnDemo_2.name)
}

//1
//2
//4
//5

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

1
2

returnDemo_2 遇到 3 時會跳過它繼續執行(有點相似循環體中的continue行爲)。最後輸出

1
2
4
5

returnDemo_2 中,咱們用一個匿名函數替代 lambda 表達式。 匿名函數內部的 return 語句將從該匿名函數自身返回。

在Kotlin中,這是匿名函數和 lambda 表達式行爲不一致的地方。固然,爲了顯式的指明 return 返回的地址,爲此 kotlin 還提供了 @Label (標籤) 來控制返回語句,且看下節分解。

3.5.7 標籤(label)

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

Kotlin 的函數是能夠被嵌套的。它有函數字面量、局部函數等。 有了標籤限制的 return,咱們就能夠從外層函數返回了。例如,從 lambda 表達式中返回,returnDemo_2() 咱們能夠顯示指定lambda 表達式中的return地址是其入口處。

代碼示例:

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

//1
//2
//4
//5

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

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

代碼示例

fun returnDemo_4() {
    println(" START " + ::returnDemo_4.name)
    val intArray = intArrayOf(1, 2, 3, 4, 5)
    intArray.forEach {
        if (it == 3) return@forEach // 從 lambda 表達式 @forEach 中返回。
        println(it)
    }

    println(" END " + ::returnDemo_4.name)
}

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

一般當咱們在循環體中使用break,是跳出最近外層的循環:

fun breakDemo_1() {
    println("---------------  breakDemo_1 ---------------")
    for (outer in 1..5) {
        println("outer=" + outer)
        for (inner in 1..10) {
            println("inner=" + inner)
            if (inner % 2 == 0) {
                break
            }
        }
    }
}

輸出

---------------  breakDemo_1 ---------------
outer=1
inner=1
inner=2
outer=2
inner=1
inner=2
outer=3
inner=1
inner=2
outer=4
inner=1
inner=2
outer=5
inner=1
inner=2

當咱們想直接跳轉到外層for循環,這個時候咱們就可使用標籤了。

代碼示例

fun breakDemo_2() {
    println("---------------  breakDemo_2 ---------------")
    outer@ for (outer in 1..5)
        for (inner in 1..10) {
            println("inner=" + inner)
            println("outer=" + outer)
            if (inner % 2 == 0) {
                break@outer
            }
        }
}

輸出

---------------  breakDemo_2 ---------------
inner=1
outer=1
inner=2
outer=1

有時候,爲了代碼可讀性,咱們能夠用標籤來顯式地指出循環體的跳轉地址,好比說在breakDemo_1()中,咱們能夠用標籤來指明內層循環的跳轉地址:

fun breakDemo_3() {
    println("---------------  breakDemo_3 ---------------")
    for (outer in 1..5)
        inner@ for (inner in 1..10) {
            println("inner=" + inner)
            println("outer=" + outer)
            if (inner % 2 == 0) {
                break@inner
            }
        }
}

3.5.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類型,沒有任何值,因此沒法當作參數傳給函數:

>>> println(ex)
error: overload resolution ambiguity: 
@InlineOnly public inline fun println(message: Any?): Unit defined in kotlin.io
@InlineOnly public inline fun println(message: Boolean): Unit defined in kotlin.io
@InlineOnly public inline fun println(message: Byte): Unit defined in kotlin.io
@InlineOnly public inline fun println(message: Char): Unit defined in kotlin.io
@InlineOnly public inline fun println(message: CharArray): Unit defined in kotlin.io
@InlineOnly public inline fun println(message: Double): Unit defined in kotlin.io
@InlineOnly public inline fun println(message: Float): Unit defined in kotlin.io
@InlineOnly public inline fun println(message: Int): Unit defined in kotlin.io
@InlineOnly public inline fun println(message: Long): Unit defined in kotlin.io
@InlineOnly public inline fun println(message: Short): Unit defined in kotlin.io
println(ex)
^

>>> ex
exception: org.jetbrains.kotlin.codegen.CompilationException: Back-end (JVM) Internal error: Unregistered script: class Line62
Cause: Unregistered script: class Line62
File being compiled and position: (1,1) in /line64.kts
PsiElement: ex
The root cause was thrown at: ScriptContext.java:86
...

3.6 代碼註釋

正如 Java 和 JavaScript,Kotlin 支持行註釋及塊註釋。

// 這是一個行註釋

/* 這是一個多行的
   塊註釋。 */

與 Java 不一樣的是,Kotlin 的塊註釋能夠嵌套。就是說,你能夠這樣註釋:

/**
 * hhhh
 * /**
 *  fff
 *  /**
 *    ggggg
 *  */
 * */
 *
 * abc
 *
 */
fun main(args:Array<String>){
    val f = Functions()
    println(f.fvoid1())
    println(f.fvoid2())
    println(f.sum1(1,1))
    println(f.sum2(1,1))
}

3.7 語法與標識符

咱們知道,任何一門編程語言都會有一些本身專用的關鍵字、符號以及規定的語法規則等等。程序員們使用這些基礎詞彙和語法規則來表達算法步驟,也就是寫代碼的過程。

詞法分析是編譯器對源碼進行編譯的基礎步驟之一。詞法分析是將源程序讀入的字符序列,按照必定的規則轉換成詞法單元(Token)序列的過程。詞法單元是語言中具備獨立意義的最小單元,包括修飾符、關鍵字、常數、運算符、邊界符等等。

3.7.1 修飾符

在Kotlin源碼工程中的kotlin/grammar/src/modifiers.grm文件中,描述了Kotlin語言的修飾符,咱們在此做簡要註釋說明:

/**
## Modifiers
*/

modifiers
  : (modifier | annotations)*
  ;

typeModifiers
  : (suspendModifier | annotations)*
  ;

modifier
  : classModifier
  : accessModifier
  : varianceAnnotation
  : memberModifier
  : parameterModifier
  : typeParameterModifier
  : functionModifier
  : propertyModifier
  ;

classModifier 類修飾符
  : "abstract" 抽象類
  : "final" 不可被繼承final類
  : "enum" 枚舉類
  : "open" 可繼承open類
  : "annotation" 註解類
  : "sealed" 密封類
  : "data" 數據類
  ;

memberModifier
  : "override" 重寫函數
  : "open" 可被重寫
  : "final" 不可被重寫
  : "abstract" 抽象函數
  : "lateinit" 後期初始化
  ;

accessModifier 訪問權限控制, 默認是public
  : "private"
  : "protected"
  : "public"
  : "internal"  整個模塊內(模塊(module)是指一塊兒編譯的一組 Kotlin 源代碼文件: 例如,一個 IntelliJ IDEA 模塊,一個 Maven 工程, 或 Gradle 工程,經過 Ant 任務的一次調用編譯的一組文件等)可訪問
  ;

varianceAnnotation 泛型可變性
  : "in" 
  : "out"
  ;

parameterModifier
  : "noinline" 
  : "crossinline" 
  : "vararg" 變長參數
  ;

typeParameterModifier
  : "reified"
  ;

functionModifier
  : "tailrec" 尾遞歸
  : "operator"
  : "infix"
  : "inline"
  : "external"
  : suspendModifier
  ;

propertyModifier
  : "const" 
  ;

suspendModifier
  : "suspend"
  ;

這些修飾符的完整定義,在kotlin/compiler/frontend/src/org/jetbrains/kotlin/lexer/KtTokens.java源碼中:

KtModifierKeywordToken[] MODIFIER_KEYWORDS_ARRAY =
            new KtModifierKeywordToken[] {
                    ABSTRACT_KEYWORD, ENUM_KEYWORD, OPEN_KEYWORD, INNER_KEYWORD, OVERRIDE_KEYWORD, PRIVATE_KEYWORD,
                    PUBLIC_KEYWORD, INTERNAL_KEYWORD, PROTECTED_KEYWORD, OUT_KEYWORD, IN_KEYWORD, FINAL_KEYWORD, VARARG_KEYWORD,
                    REIFIED_KEYWORD, COMPANION_KEYWORD, SEALED_KEYWORD, LATEINIT_KEYWORD,
                    DATA_KEYWORD, INLINE_KEYWORD, NOINLINE_KEYWORD, TAILREC_KEYWORD, EXTERNAL_KEYWORD, ANNOTATION_KEYWORD, CROSSINLINE_KEYWORD,
                    CONST_KEYWORD, OPERATOR_KEYWORD, INFIX_KEYWORD, SUSPEND_KEYWORD, HEADER_KEYWORD, IMPL_KEYWORD
            };

    TokenSet MODIFIER_KEYWORDS = TokenSet.create(MODIFIER_KEYWORDS_ARRAY);

    TokenSet TYPE_MODIFIER_KEYWORDS = TokenSet.create(SUSPEND_KEYWORD);
    TokenSet TYPE_ARGUMENT_MODIFIER_KEYWORDS = TokenSet.create(IN_KEYWORD, OUT_KEYWORD);
    TokenSet RESERVED_VALUE_PARAMETER_MODIFIER_KEYWORDS = TokenSet.create(OUT_KEYWORD, VARARG_KEYWORD);

    TokenSet VISIBILITY_MODIFIERS = TokenSet.create(PRIVATE_KEYWORD, PUBLIC_KEYWORD, INTERNAL_KEYWORD, PROTECTED_KEYWORD);

3.7.2 關鍵字(保留字)

TokenSet KEYWORDS = TokenSet.create(PACKAGE_KEYWORD, AS_KEYWORD, TYPE_ALIAS_KEYWORD, CLASS_KEYWORD, INTERFACE_KEYWORD,
                                        THIS_KEYWORD, SUPER_KEYWORD, VAL_KEYWORD, VAR_KEYWORD, FUN_KEYWORD, FOR_KEYWORD,
                                        NULL_KEYWORD,
                                        TRUE_KEYWORD, FALSE_KEYWORD, IS_KEYWORD,
                                        IN_KEYWORD, THROW_KEYWORD, RETURN_KEYWORD, BREAK_KEYWORD, CONTINUE_KEYWORD, OBJECT_KEYWORD, IF_KEYWORD,
                                        ELSE_KEYWORD, WHILE_KEYWORD, DO_KEYWORD, TRY_KEYWORD, WHEN_KEYWORD,
                                        NOT_IN, NOT_IS, AS_SAFE,
                                        TYPEOF_KEYWORD
    );

    TokenSet SOFT_KEYWORDS = TokenSet.create(FILE_KEYWORD, IMPORT_KEYWORD, WHERE_KEYWORD, BY_KEYWORD, GET_KEYWORD,
                                             SET_KEYWORD, ABSTRACT_KEYWORD, ENUM_KEYWORD, OPEN_KEYWORD, INNER_KEYWORD,
                                             OVERRIDE_KEYWORD, PRIVATE_KEYWORD, PUBLIC_KEYWORD, INTERNAL_KEYWORD, PROTECTED_KEYWORD,
                                             CATCH_KEYWORD, FINALLY_KEYWORD, OUT_KEYWORD, FINAL_KEYWORD, VARARG_KEYWORD, REIFIED_KEYWORD,
                                             DYNAMIC_KEYWORD, COMPANION_KEYWORD, CONSTRUCTOR_KEYWORD, INIT_KEYWORD, SEALED_KEYWORD,
                                             FIELD_KEYWORD, PROPERTY_KEYWORD, RECEIVER_KEYWORD, PARAM_KEYWORD, SETPARAM_KEYWORD,
                                             DELEGATE_KEYWORD,
                                             LATEINIT_KEYWORD,
                                             DATA_KEYWORD, INLINE_KEYWORD, NOINLINE_KEYWORD, TAILREC_KEYWORD, EXTERNAL_KEYWORD,
                                             ANNOTATION_KEYWORD, CROSSINLINE_KEYWORD, CONST_KEYWORD, OPERATOR_KEYWORD, INFIX_KEYWORD,
                                             SUSPEND_KEYWORD, HEADER_KEYWORD, IMPL_KEYWORD
    );

其中,對應的關鍵字以下:

KtKeywordToken PACKAGE_KEYWORD          = KtKeywordToken.keyword("package");
    KtKeywordToken AS_KEYWORD               = KtKeywordToken.keyword("as");
    KtKeywordToken TYPE_ALIAS_KEYWORD       = KtKeywordToken.keyword("typealias");
    KtKeywordToken CLASS_KEYWORD            = KtKeywordToken.keyword("class");
    KtKeywordToken THIS_KEYWORD             = KtKeywordToken.keyword("this");
    KtKeywordToken SUPER_KEYWORD            = KtKeywordToken.keyword("super");
    KtKeywordToken VAL_KEYWORD              = KtKeywordToken.keyword("val");
    KtKeywordToken VAR_KEYWORD              = KtKeywordToken.keyword("var");
    KtKeywordToken FUN_KEYWORD              = KtKeywordToken.keyword("fun");
    KtKeywordToken FOR_KEYWORD              = KtKeywordToken.keyword("for");
    KtKeywordToken NULL_KEYWORD             = KtKeywordToken.keyword("null");
    KtKeywordToken TRUE_KEYWORD             = KtKeywordToken.keyword("true");
    KtKeywordToken FALSE_KEYWORD            = KtKeywordToken.keyword("false");
    KtKeywordToken IS_KEYWORD               = KtKeywordToken.keyword("is");
    KtModifierKeywordToken IN_KEYWORD       = KtModifierKeywordToken.keywordModifier("in");
    KtKeywordToken THROW_KEYWORD            = KtKeywordToken.keyword("throw");
    KtKeywordToken RETURN_KEYWORD           = KtKeywordToken.keyword("return");
    KtKeywordToken BREAK_KEYWORD            = KtKeywordToken.keyword("break");
    KtKeywordToken CONTINUE_KEYWORD         = KtKeywordToken.keyword("continue");
    KtKeywordToken OBJECT_KEYWORD           = KtKeywordToken.keyword("object");
    KtKeywordToken IF_KEYWORD               = KtKeywordToken.keyword("if");
    KtKeywordToken TRY_KEYWORD              = KtKeywordToken.keyword("try");
    KtKeywordToken ELSE_KEYWORD             = KtKeywordToken.keyword("else");
    KtKeywordToken WHILE_KEYWORD            = KtKeywordToken.keyword("while");
    KtKeywordToken DO_KEYWORD               = KtKeywordToken.keyword("do");
    KtKeywordToken WHEN_KEYWORD             = KtKeywordToken.keyword("when");
    KtKeywordToken INTERFACE_KEYWORD        = KtKeywordToken.keyword("interface");

    // Reserved for future use:
    KtKeywordToken TYPEOF_KEYWORD           = KtKeywordToken.keyword("typeof");
...
    KtKeywordToken FILE_KEYWORD    = KtKeywordToken.softKeyword("file");
    KtKeywordToken FIELD_KEYWORD     = KtKeywordToken.softKeyword("field");
    KtKeywordToken PROPERTY_KEYWORD     = KtKeywordToken.softKeyword("property");
    KtKeywordToken RECEIVER_KEYWORD     = KtKeywordToken.softKeyword("receiver");
    KtKeywordToken PARAM_KEYWORD     = KtKeywordToken.softKeyword("param");
    KtKeywordToken SETPARAM_KEYWORD  = KtKeywordToken.softKeyword("setparam");
    KtKeywordToken DELEGATE_KEYWORD  = KtKeywordToken.softKeyword("delegate");
    KtKeywordToken IMPORT_KEYWORD    = KtKeywordToken.softKeyword("import");
    KtKeywordToken WHERE_KEYWORD     = KtKeywordToken.softKeyword("where");
    KtKeywordToken BY_KEYWORD        = KtKeywordToken.softKeyword("by");
    KtKeywordToken GET_KEYWORD       = KtKeywordToken.softKeyword("get");
    KtKeywordToken SET_KEYWORD       = KtKeywordToken.softKeyword("set");
    KtKeywordToken CONSTRUCTOR_KEYWORD = KtKeywordToken.softKeyword("constructor");
    KtKeywordToken INIT_KEYWORD        = KtKeywordToken.softKeyword("init");

    KtModifierKeywordToken ABSTRACT_KEYWORD  = KtModifierKeywordToken.softKeywordModifier("abstract");
    KtModifierKeywordToken ENUM_KEYWORD      = KtModifierKeywordToken.softKeywordModifier("enum");
    KtModifierKeywordToken OPEN_KEYWORD      = KtModifierKeywordToken.softKeywordModifier("open");
    KtModifierKeywordToken INNER_KEYWORD     = KtModifierKeywordToken.softKeywordModifier("inner");
    KtModifierKeywordToken OVERRIDE_KEYWORD  = KtModifierKeywordToken.softKeywordModifier("override");
    KtModifierKeywordToken PRIVATE_KEYWORD   = KtModifierKeywordToken.softKeywordModifier("private");
    KtModifierKeywordToken PUBLIC_KEYWORD    = KtModifierKeywordToken.softKeywordModifier("public");
    KtModifierKeywordToken INTERNAL_KEYWORD  = KtModifierKeywordToken.softKeywordModifier("internal");
    KtModifierKeywordToken PROTECTED_KEYWORD = KtModifierKeywordToken.softKeywordModifier("protected");
    KtKeywordToken CATCH_KEYWORD     = KtKeywordToken.softKeyword("catch");
    KtModifierKeywordToken OUT_KEYWORD       = KtModifierKeywordToken.softKeywordModifier("out");
    KtModifierKeywordToken VARARG_KEYWORD    = KtModifierKeywordToken.softKeywordModifier("vararg");
    KtModifierKeywordToken REIFIED_KEYWORD   = KtModifierKeywordToken.softKeywordModifier("reified");
    KtKeywordToken DYNAMIC_KEYWORD   = KtKeywordToken.softKeyword("dynamic");
    KtModifierKeywordToken COMPANION_KEYWORD = KtModifierKeywordToken.softKeywordModifier("companion");
    KtModifierKeywordToken SEALED_KEYWORD    = KtModifierKeywordToken.softKeywordModifier("sealed");

    KtModifierKeywordToken DEFAULT_VISIBILITY_KEYWORD = PUBLIC_KEYWORD;

    KtKeywordToken FINALLY_KEYWORD   = KtKeywordToken.softKeyword("finally");
    KtModifierKeywordToken FINAL_KEYWORD     = KtModifierKeywordToken.softKeywordModifier("final");

    KtModifierKeywordToken LATEINIT_KEYWORD = KtModifierKeywordToken.softKeywordModifier("lateinit");

    KtModifierKeywordToken DATA_KEYWORD    = KtModifierKeywordToken.softKeywordModifier("data");
    KtModifierKeywordToken INLINE_KEYWORD    = KtModifierKeywordToken.softKeywordModifier("inline");
    KtModifierKeywordToken NOINLINE_KEYWORD    = KtModifierKeywordToken.softKeywordModifier("noinline");
    KtModifierKeywordToken TAILREC_KEYWORD    = KtModifierKeywordToken.softKeywordModifier("tailrec");
    KtModifierKeywordToken EXTERNAL_KEYWORD    = KtModifierKeywordToken.softKeywordModifier("external");
    KtModifierKeywordToken ANNOTATION_KEYWORD    = KtModifierKeywordToken.softKeywordModifier("annotation");
    KtModifierKeywordToken CROSSINLINE_KEYWORD    = KtModifierKeywordToken.softKeywordModifier("crossinline");
    KtModifierKeywordToken OPERATOR_KEYWORD = KtModifierKeywordToken.softKeywordModifier("operator");
    KtModifierKeywordToken INFIX_KEYWORD = KtModifierKeywordToken.softKeywordModifier("infix");

    KtModifierKeywordToken CONST_KEYWORD = KtModifierKeywordToken.softKeywordModifier("const");

    KtModifierKeywordToken SUSPEND_KEYWORD = KtModifierKeywordToken.softKeywordModifier("suspend");

    KtModifierKeywordToken HEADER_KEYWORD = KtModifierKeywordToken.softKeywordModifier("header");
    KtModifierKeywordToken IMPL_KEYWORD = KtModifierKeywordToken.softKeywordModifier("impl");

this 關鍵字

this關鍵字持有當前對象的引用。咱們可使用this來引用變量或者成員函數,也可使用return this,來返回某個類的引用。

代碼示例

class ThisDemo {
    val thisis = "THIS IS"

    fun whatIsThis(): ThisDemo {
        println(this.thisis) //引用變量
        this.howIsThis()// 引用成員函數
        return this // 返回此類的引用
    }

    fun howIsThis(){
        println("HOW IS THIS ?")
    }
}

測試代碼

@Test
    fun testThisDemo(){
        val demo = ThisDemo()
        println(demo.whatIsThis())
    }

輸出

THIS IS
HOW IS THIS ?
com.easy.kotlin.ThisDemo@475232fc

在類的成員中,this 指向的是該類的當前對象。

在擴展函數或者帶接收者的函數字面值中, this 表示在點左側傳遞的 接收者參數。

代碼示例:

>>> val sum = fun Int.(x:Int):Int = this + x
>>> sum
kotlin.Int.(kotlin.Int) -> kotlin.Int
>>> 1.sum(1)
2
>>> val concat = fun String.(x:Any) = this + x
>>> "abc".concat(123)
abc123
>>> "abc".concat(true)
abctrue

若是 this 沒有限定符,它指的是最內層的包含它的做用域。若是咱們想要引用其餘做用域中的 this,可使用 this@label 標籤。

代碼示例:

class Outer {
    val oh = "Oh!"

    inner class Inner {

        fun m() {
            val outer = this@Outer
            val inner = this@Inner
            val pthis = this
            println("outer=" + outer)
            println("inner=" + inner)
            println("pthis=" + pthis)
            println(this@Outer.oh)

            val fun1 = hello@ fun String.() {
                val d1 = this // fun1 的接收者
                println("d1" + d1)
            }

            val fun2 = { s: String ->
                val d2 = this
                println("d2=" + d2)
            }

            "abc".fun1()

            fun2

        }
    }


}

測試代碼:

@Test
    fun testThisKeyWord() {
        val outer = Outer()
        outer.Inner().m()
    }

輸出

outer=com.easy.kotlin.Outer@5114e183
inner=com.easy.kotlin.Outer$Inner@5aa8ac7f
pthis=com.easy.kotlin.Outer$Inner@5aa8ac7f
Oh!
d1abc

super 關鍵字

super關鍵字持有指向其父類的引用。

代碼示例:

open class Father {
    open val firstName = "Chen"
    open val lastName = "Jason"

    fun ff() {
        println("FFF")
    }
}

class Son : Father {
    override var firstName = super.firstName
    override var lastName = "Jack"

    constructor(lastName: String) {
        this.lastName = lastName
    }

    fun love() {
        super.ff() // 調用父類方法
        println(super.firstName + " " + super.lastName + " Love " + this.firstName + " " + this.lastName)
    }
}

測試代碼

@Test
    fun testSuperKeyWord() {
        val son = Son("Harry")
        son.love()
    }

輸出

FFF
Chen Jason Love Chen Harry

3.7.3 操做符和操做符的重載

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", ",");

3.7.4 操做符優先級(Precedence)

優先級 標題 符號
最高 後綴(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) ll
最低 賦值(Assignment) =, +=, -=, *=, /=, %=

注:Markdown表格語法:ll||

爲實現這些的操做符,Kotlin爲二元操做符左側的類型和一元操做符的參數類型,提供了相應的函數或擴展函數。

例如在kotlin/core/builtins/native/kotlin/Primitives.kt代碼中,對基本類型Int的操做符的實現代碼以下

public class Int private constructor() : Number(), Comparable<Int> {
    ...

    /**
     * Compares this value with the specified value for order.
     * Returns zero if this value is equal to the specified other value, a negative number if it's less than other,
     * or a positive number if it's greater than other.
     */
    public operator fun compareTo(other: Byte): Int

    /**
     * Compares this value with the specified value for order.
     * Returns zero if this value is equal to the specified other value, a negative number if it's less than other,
     * or a positive number if it's greater than other.
     */
    public operator fun compareTo(other: Short): Int

    /**
     * Compares this value with the specified value for order.
     * Returns zero if this value is equal to the specified other value, a negative number if it's less than other,
     * or a positive number if it's greater than other.
     */
    public override operator fun compareTo(other: Int): Int

    /**
     * Compares this value with the specified value for order.
     * Returns zero if this value is equal to the specified other value, a negative number if it's less than other,
     * or a positive number if it's greater than other.
     */
    public operator fun compareTo(other: Long): Int

    /**
     * Compares this value with the specified value for order.
     * Returns zero if this value is equal to the specified other value, a negative number if it's less than other,
     * or a positive number if it's greater than other.
     */
    public operator fun compareTo(other: Float): Int

    /**
     * Compares this value with the specified value for order.
     * Returns zero if this value is equal to the specified other value, a negative number if it's less than other,
     * or a positive number if it's greater than other.
     */
    public operator fun compareTo(other: Double): Int

    /** Adds the other value to this value. */
    public operator fun plus(other: Byte): Int
    /** Adds the other value to this value. */
    public operator fun plus(other: Short): Int
    /** Adds the other value to this value. */
    public operator fun plus(other: Int): Int
    /** Adds the other value to this value. */
    public operator fun plus(other: Long): Long
    /** Adds the other value to this value. */
    public operator fun plus(other: Float): Float
    /** Adds the other value to this value. */
    public operator fun plus(other: Double): Double

    /** Subtracts the other value from this value. */
    public operator fun minus(other: Byte): Int
    /** Subtracts the other value from this value. */
    public operator fun minus(other: Short): Int
    /** Subtracts the other value from this value. */
    public operator fun minus(other: Int): Int
    /** Subtracts the other value from this value. */
    public operator fun minus(other: Long): Long
    /** Subtracts the other value from this value. */
    public operator fun minus(other: Float): Float
    /** Subtracts the other value from this value. */
    public operator fun minus(other: Double): Double

    /** Multiplies this value by the other value. */
    public operator fun times(other: Byte): Int
    /** Multiplies this value by the other value. */
    public operator fun times(other: Short): Int
    /** Multiplies this value by the other value. */
    public operator fun times(other: Int): Int
    /** Multiplies this value by the other value. */
    public operator fun times(other: Long): Long
    /** Multiplies this value by the other value. */
    public operator fun times(other: Float): Float
    /** Multiplies this value by the other value. */
    public operator fun times(other: Double): Double

    /** Divides this value by the other value. */
    public operator fun div(other: Byte): Int
    /** Divides this value by the other value. */
    public operator fun div(other: Short): Int
    /** Divides this value by the other value. */
    public operator fun div(other: Int): Int
    /** Divides this value by the other value. */
    public operator fun div(other: Long): Long
    /** Divides this value by the other value. */
    public operator fun div(other: Float): Float
    /** Divides this value by the other value. */
    public operator fun div(other: Double): Double

    /** Calculates the remainder of dividing this value by the other value. */
    @Deprecated("Use rem(other) instead", ReplaceWith("rem(other)"), DeprecationLevel.WARNING)
    public operator fun mod(other: Byte): Int
    /** Calculates the remainder of dividing this value by the other value. */
    @Deprecated("Use rem(other) instead", ReplaceWith("rem(other)"), DeprecationLevel.WARNING)
    public operator fun mod(other: Short): Int
    /** Calculates the remainder of dividing this value by the other value. */
    @Deprecated("Use rem(other) instead", ReplaceWith("rem(other)"), DeprecationLevel.WARNING)
    public operator fun mod(other: Int): Int
    /** Calculates the remainder of dividing this value by the other value. */
    @Deprecated("Use rem(other) instead", ReplaceWith("rem(other)"), DeprecationLevel.WARNING)
    public operator fun mod(other: Long): Long
    /** Calculates the remainder of dividing this value by the other value. */
    @Deprecated("Use rem(other) instead", ReplaceWith("rem(other)"), DeprecationLevel.WARNING)
    public operator fun mod(other: Float): Float
    /** Calculates the remainder of dividing this value by the other value. */
    @Deprecated("Use rem(other) instead", ReplaceWith("rem(other)"), DeprecationLevel.WARNING)
    public operator fun mod(other: Double): Double

    /** Calculates the remainder of dividing this value by the other value. */
    @SinceKotlin("1.1")
    public operator fun rem(other: Byte): Int
    /** Calculates the remainder of dividing this value by the other value. */
    @SinceKotlin("1.1")
    public operator fun rem(other: Short): Int
    /** Calculates the remainder of dividing this value by the other value. */
    @SinceKotlin("1.1")
    public operator fun rem(other: Int): Int
    /** Calculates the remainder of dividing this value by the other value. */
    @SinceKotlin("1.1")
    public operator fun rem(other: Long): Long
    /** Calculates the remainder of dividing this value by the other value. */
    @SinceKotlin("1.1")
    public operator fun rem(other: Float): Float
    /** Calculates the remainder of dividing this value by the other value. */
    @SinceKotlin("1.1")
    public operator fun rem(other: Double): Double

    /** Increments this value. */
    public operator fun inc(): Int
    /** Decrements this value. */
    public operator fun dec(): Int
    /** Returns this value. */
    public operator fun unaryPlus(): Int
    /** Returns the negative of this value. */
    public operator fun unaryMinus(): Int

     /** Creates a range from this value to the specified [other] value. */
    public operator fun rangeTo(other: Byte): IntRange
     /** Creates a range from this value to the specified [other] value. */
    public operator fun rangeTo(other: Short): IntRange
     /** Creates a range from this value to the specified [other] value. */
    public operator fun rangeTo(other: Int): IntRange
     /** Creates a range from this value to the specified [other] value. */
    public operator fun rangeTo(other: Long): LongRange

    /** Shifts this value left by [bits]. */
    public infix fun shl(bitCount: Int): Int
    /** Shifts this value right by [bits], filling the leftmost bits with copies of the sign bit. */
    public infix fun shr(bitCount: Int): Int
    /** Shifts this value right by [bits], filling the leftmost bits with zeros. */
    public infix fun ushr(bitCount: Int): Int
    /** Performs a bitwise AND operation between the two values. */
    public infix fun and(other: Int): Int
    /** Performs a bitwise OR operation between the two values. */
    public infix fun or(other: Int): Int
    /** Performs a bitwise XOR operation between the two values. */
    public infix fun xor(other: Int): Int
    /** Inverts the bits in this value. */
    public fun inv(): Int

    public override fun toByte(): Byte
    public override fun toChar(): Char
    public override fun toShort(): Short
    public override fun toInt(): Int
    public override fun toLong(): Long
    public override fun toFloat(): Float
    public override fun toDouble(): Double
}

從源代碼咱們能夠看出,重載操做符的函數須要用 operator 修飾符標記。中綴操做符的函數使用infix修飾符標記。

3.7.5 一元操做符(unary operation)

前綴操做符

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

例如,當編譯器處理表達式 +a 時,它將執行如下步驟:

  • 肯定 a 的類型,令其爲 T
  • 爲接收者 T 查找一個帶有 operator 修飾符的無參函數 unaryPlus(),即成員函數或擴展函數。
  • 若是函數不存在或不明確,則致使編譯錯誤。
  • 若是函數存在且其返回類型爲 R,那就表達式 +a 具備類型 R

編譯器對這些操做以及全部其餘操做都針對基本類型作了優化,不會引入函數調用的開銷。

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

package com.easy.kotlin

/**
 * Created by jack on 2017/6/10.
 */

class OperatorDemo {

}


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

/**
 * Created by jack on 2017/6/10.
 */
@RunWith(JUnit4::class)
class OperatorDemoTest {

    @Test
    fun testPointUnaryMinus() {
        val p = Point(1, 1)
        val np = -p
        println(np) //Point(x=-1, y=-1)
    }
}

遞增和遞減

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

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

編譯器執行如下步驟來解析後綴形式的操做符,例如 a++

  • 肯定 a 的類型,令其爲 T
  • 查找一個適用於類型爲 T 的接收者的、帶有 operator 修飾符的無參數函數 inc()
  • 檢查函數的返回類型是 T 的子類型。

計算表達式的步驟是:

  • a 的初始值存儲到臨時存儲 a_
  • a.inc() 結果賦值給 a
  • a_ 做爲表達式的結果返回

( a-- 同理分析)。

對於前綴形式 ++a--a 解析步驟相似,可是返回值是取的新值來返回:

  • a.inc() 結果賦值給 a
  • a 的新值a+1做爲表達式結果返回

( --a 同理分析)。

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

/**
 * Created by jack on 2017/6/10.
 */
@RunWith(JUnit4::class)
class OperatorDemoTest 
    @Test
    fun testCounterIndexPlus() {
        val c = Counter(1)
        val cplus = c + 10
        println(cplus) //Counter(index=11)
    }
}

in操做符

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

索引訪問操做符

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

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

調用操做符

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

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

計算並賦值

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

比較操做符

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

/**
 * Created by jack on 2017/6/11.
 */
@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)

3.8 函數擴展和屬性擴展(Extensions)

Kotlin 支持 擴展函數 和 擴展屬性。其可以擴展一個類的新功能而無需繼承該類或使用像裝飾者這樣的設計模式等。

大多數時候咱們在頂層定義擴展,即直接在包裏:

package com.easy.kotlin

/**
 * Created by jack on 2017/6/11.
 */

val <T> List<T>.lastIndex: Int get() = size - 1

fun String.notEmpty(): Boolean {
    return !this.isEmpty()
}

這樣咱們就能夠在整個包裏使用這些擴展。

要使用其餘包的擴展,咱們須要在調用方導入它:

package com.example.usage

import foo.bar.goo // 導入全部名爲「goo」的擴展
                   // 或者
import foo.bar.*   // 從「foo.bar」導入一切

fun usage(baz: Baz) {
    baz.goo()
}

3.8.1 擴展函數

聲明一個擴展函數,咱們須要用_被擴展的類型_來做爲前綴。

好比說,咱們不喜歡相似下面的_雙重否認_式的邏輯判斷(繞腦子):

>>> !"123".isEmpty()
true

咱們就能夠爲String類型擴展一個notEmpty()函數:

>>> fun String.notEmpty():Boolean{
... return !this.isEmpty()
... }

>>> "".notEmpty()
false

>>> "123".notEmpty()
true

下面代碼爲 MutableList<Int> 添加一個swap 函數:

fun MutableList<Int>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // this對應該列表
    this[index1] = this[index2]
    this[index2] = tmp
}

這個 this 關鍵字在擴展函數內部對應到接收者對象(傳過來的在點.符號前的對象)
如今,咱們對任意 MutableList<Int> 調用該函數了。

固然,這個函數對任何 MutableList<T> 起做用,咱們能夠泛化它:

fun <T> MutableList<T>.mswap(index1: Int, index2: Int) {
        val tmp = this[index1] // 「this」對應該列表
        this[index1] = this[index2]
        this[index2] = tmp
    }

爲了在接收者類型表達式中使用泛型,咱們要在函數名前聲明泛型參數。

完整代碼示例

package com.easy.kotlin

/**
 * Created by jack on 2017/6/11.
 */

val <T> List<T>.lastIndex: Int get() = size - 1

fun String.notEmpty(): Boolean {
    return !this.isEmpty()
}

fun MutableList<Int>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // this對應該列表m
    this[index1] = this[index2]
    this[index2] = tmp
}

fun <T> MutableList<T>.mswap(index1: Int, index2: Int) {
    val tmp = this[index1] // 「this」對應該列表
    this[index1] = this[index2]
    this[index2] = tmp
}


class ExtensionsDemo {


    fun useExtensions() {
        val a = "abc"
        println(a.notEmpty())//true

        val mList = mutableListOf<Int>(1, 2, 3, 4, 5)
        println("Before Swap:")
        println(mList)//[1, 2, 3, 4, 5]
        mList.swap(0, mList.size - 1)
        println("After Swap:")
        println(mList)//[5, 2, 3, 4, 1]


        val mmList = mutableListOf<String>("a12", "b34", "c56", "d78")
        println("Before Swap:")
        println(mmList)//[a12, b34, c56, d78]
        mmList.mswap(1, 2)
        println("After Swap:")
        println(mmList)//[a12, c56, b34, d78]


        val mmmList = mutableListOf<Int>(100, 200, 300, 400, 500)
        println("Before Swap:")
        println(mmmList)
        mmmList.mswap(0, mmmList.lastIndex)
        println("After Swap:")
        println(mmmList)
    }


    class Inner {
        fun useExtensions() {
            val mmmList = mutableListOf<Int>(100, 200, 300, 400, 500)
            println(mmmList.lastIndex)
        }
    }
}

測試代碼

package com.easy.kotlin

import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

/**
 * Created by jack on 2017/6/11.
 */

@RunWith(JUnit4::class)
class ExtensionsDemoTest {
    @Test fun testExtensionsDemo() {
        val demo = ExtensionsDemo()
        demo.useExtensions()
    }
}

擴展不是真正的修改他們所擴展的類。咱們定義一個擴展,其實並無在一個類中插入新函數,僅僅是經過該類型的變量,用點.表達式去調用這個新函數。

3.8.2 擴展屬性

和函數相似,Kotlin 支持擴展屬性:

val <T> List<T>.lastIndex: Int
    get() = size - 1

注意:因爲擴展沒有實際的將成員插入類中,所以對擴展的屬性來講,它的行爲只能由顯式提供的 getters/setters 定義。

代碼示例:

package com.easy.kotlin

/**
 * Created by jack on 2017/6/11.
 */

val <T> List<T>.lastIndex: Int get() = size - 1

咱們能夠直接使用包com.easy.kotlin中擴展的屬性lastIndex :

螢幕快照 2017-06-11 02.54.03.png

3.9 空指針安全(Null-safety)

咱們寫代碼的時候知道,在Java中NPE(NullPointerExceptions)是一件成程序員幾近崩潰的事情。不少時候,雖然費盡體力腦力,仍然防不勝防。

之前,當咱們不肯定一個DTO類中的字段是否已初始化時,可使用@Nullable和@NotNull註解來聲明,但功能頗有限。

如今好了,Kotlin在編譯器級別,把你以前在Java中須要寫的null check代碼完成了。

可是,當咱們的代碼

  • 顯式調用 throw NullPointerException()
  • 使用了 !! 操做符
  • 調用的外部 Java 代碼有NPE
  • 對於初始化,有一些數據不一致(如一個未初始化的 this 用於構造函數的某個地方)

也可能會發生NPE。

在Kotlin中null等同於空指針。咱們來經過代碼來看一下null的有趣的特性:

首先,一個非空引用不能直接賦值爲null :

>>> var a="abc"
>>> a=null
error: null can not be a value of a non-null type String
a=null
  ^

>>> var one=1
>>> one=null
error: null can not be a value of a non-null type Int
one=null
    ^

>>> var arrayInts = intArrayOf(1,2,3)
>>> arrayInts=null
error: null can not be a value of a non-null type IntArray
arrayInts=null
          ^

這樣,咱們就能夠放心地調用 a 的方法或者訪問它的屬性,不會致使 NPE

>>> val a="abc"
>>> a.length
3

若是要容許爲空,咱們能夠在變量的類型後面加個問號?聲明一個變量爲可空的:

>>> var a:String?="abc"
>>> a=null
>>> var one:Int?=1
>>> one=null
>>> var arrayInts:IntArray?=intArrayOf(1,2,3)
>>> arrayInts=null
>>> arrayInts
null

若是咱們聲明瞭一個可空String?類型變量na ,而後直接調用length屬性,這將是不安全的。編譯器會直接報錯:

>>> var na:String?="abc"
>>> na=null
>>> na.length
error: only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?
na.length
  ^

咱們使用安全調用?. 和 非空斷言調用 !!.

>>> na?.length
null
>>> na!!.length
kotlin.KotlinNullPointerException

咱們能夠看出,代碼返回了nullkotlin.KotlinNullPointerException

安全調用在鏈式調用中頗有用。在調用鏈中若是任意一個屬性(環節)爲空,這個鏈式調用就會安全返回 null。

若是要只對非空值執行某個操做,安全調用操做符能夠與 let (以調用者的值做爲參數來執行指定的函數塊,並返回其結果)一塊兒使用:

>>> val listWithNulls: List<String?> = listOf("A", "B",null)
>>> listWithNulls
[A, B, null]

>>> listWithNulls.forEach{
... it?.let{println(it)}
... }
A
B

參考資料

1.https://www.kotlincn.net/docs...

2.https://en.wikipedia.org/wiki...

相關文章
相關標籤/搜索