Kotlin語法手冊(一)

Kotlin語法手冊(一)

在使用kotlin時壮吩,由于掌握的不夠牢靠蟆技,好多時候也還是Java編程的習(xí)慣,浪費了kotlin提供的語言特性沿侈,方便性闯第,間接性,在閱讀一些Android開源庫的時候缀拭,由于好多都是kotlin語法編寫的咳短,看的比較費勁,還得去查閱kotlin的語法蛛淋,比較不方便咙好,故把kotlin的語法記錄下來,方便查閱溫故褐荷,鞏固自己的基礎(chǔ)知識勾效。

變量

kotlin 中,變量分為 可變變量(var)不可變變量(val) 兩類叛甫。

  • val:不可變引用层宫,對應(yīng)的是 Java 中的 final 變量;使用 val 聲明的變量不能在初始化之后再次賦值其监。
  • var:可變引用萌腿,對應(yīng)的是 Java 中的非 final 變量;使用 var 聲明的變量的值可以被改變抖苦。

不可變變量在賦值之后就不能再去改變它的狀態(tài)了毁菱,因此不可變變量可以說是線程安全的,因為它們無法改變锌历,所有線程訪問到的對象都是同一個贮庞,因此也不需要去做訪問控制。開發(fā)者應(yīng)當(dāng)盡可能地使用不可變變量究西,這樣可以讓代碼更加接近函數(shù)式編程風(fēng)格贸伐。

fun main() {
   
    //聲明一個整數(shù)類型的不可變變量,初始化后intValue的值不能再改變了
    val intValue: Int = 100

    //聲明一個雙精度類型的可變變量
    var doubleValue: Double = 100.0
}

在聲明變量時怔揩,通常不需要顯式指明變量的類型捉邢,可以由編譯器根據(jù)上下文自動推導(dǎo)出來。如果只讀變量在聲明時沒有初始的默認(rèn)值商膊,則必須指明變量類型伏伐,且在使用前必須確保在各個分支條件下變量可以被初始化,否則編譯器就會報異常晕拆。

數(shù)據(jù)類型

基本數(shù)據(jù)類型

在 kotlin中藐翎,一切都是對象材蹬,可以在任何變量上調(diào)用其成員函數(shù)和屬性,并不區(qū)分基本數(shù)據(jù)類型和它的包裝類吝镣。也就是說kotlin 沒有像 Java 中那樣的原始基本類型堤器,但 byte、char末贾、integer闸溃、float 或者 boolean 等類型仍然有保留,但是全部都作為對象存在拱撵。

    //在 kotlin 中辉川,int、long拴测、float 等類型仍然存在乓旗,但是是作為對象存在的

    val intIndex: Int = 100
    //等價于,編譯器會自動進(jìn)行類型推導(dǎo)
    val intIndex = 100

    //數(shù)字類型不會自動轉(zhuǎn)型集索,必須要進(jìn)行明確的類型轉(zhuǎn)換
    val doubleIndex: Double = intIndex.toDouble()
    //以下代碼會提示錯誤屿愚,需要進(jìn)行明確的類型轉(zhuǎn)換
    //val doubleIndex: Double = intIndex

    val intValue: Int = 1
    val longValue: Long = 1
    //以下代碼會提示錯誤,因為兩者的數(shù)據(jù)類型不一致务荆,需要轉(zhuǎn)換為同一類型后才能進(jìn)行比較
    //println(intValue == longValue)

    //Char 不能直接作為數(shù)字來處理渺鹦,需要主動轉(zhuǎn)換
    val ch: Char = 'c'
    val charValue: Int = ch.toInt()
    //以下代碼會提示錯誤
    //val charValue: Int = ch

    //不支持八進(jìn)制
    //二進(jìn)制
    val value1 = 0b00101
    //十六進(jìn)制
    val value2 = 0x123

字符串

字符串用 String 類型表示。字符串是不可變的蛹含。字符串的元素:使用索引運算符訪問: s[i];可以用 for 循環(huán)迭代字符串,也可以用 + 來連接字符串塞颁。

    val str = "hello"
    println(str[1])
    for (c in str) {
        println(c)
    }
    val str1 = str + " world"

kotlin 支持在字符串字面值中引用局部變量浦箱,只需要在變量名前加上字符 $ 即可,此外還可以包含用花括號{}括起來的表達(dá)式祠锣,此時會自動求值并把結(jié)果合并到字符串中酷窥。

    val intValue = 100
    //可以直接包含變量
    println("intValue value is $intValue") //intValue value is 100
    //也可以包含表達(dá)式
    println("(intValue + 100) value is ${intValue + 100}")   //(intValue + 100) value is 200

如果你需要在原始字符串中表示字面值($)字符(它不支持反斜杠轉(zhuǎn)義),可以用下列語法:

    val price = "${'$'}100.99"
    println(price)  //$100.99

數(shù)組

數(shù)組在 Kotlin 中使用 Array 類來表示伴网,它定義了 getset 函數(shù)(按照運算符重載約定這會轉(zhuǎn)變?yōu)?[])以及 size 屬性蓬推,以及一些其他有用的成員函數(shù):

  1. arrayOf():使用庫函數(shù) arrayOf() 來創(chuàng)建一個數(shù)組并傳遞元素值給它,如: arrayOf(1, 2, 3) 就創(chuàng)建了 [1, 2, 3]澡腾。
  2. arrayOfNulls:庫函數(shù) arrayOfNulls() 可以用于創(chuàng)建一個指定大小的沸伏、初始化所有元素都為空的數(shù)組。
  3. Array:Array是接受數(shù)組大小以及一個函數(shù)參數(shù)的構(gòu)造函數(shù)动分,用作參數(shù)的函數(shù)能夠返回給定索引的每個元素初始值毅糟,如下所示:
// 創(chuàng)建一個 Array<String> 數(shù)組大小為5,函數(shù)為(i * i).toString()的字符串?dāng)?shù)組
val asc = Array(5) { i -> (i * i).toString() }//["0", "1", "4", "9", "16"]
asc.forEach { println(it) }

基本數(shù)據(jù)類型數(shù)組

數(shù)組類型的類型參數(shù)如上面的Array<String>澜公,始終會變成對象類型姆另,因此聲明 Array< Int > 將是一個包含裝箱類型(java.lang.Integer)的數(shù)組,如果想要創(chuàng)建沒有裝箱的基本數(shù)據(jù)類型的數(shù)組,必須使用一個基本數(shù)據(jù)類型數(shù)組的特殊類IntArray迹辐、ByteArray蝶防、BooleanArray 等,這些類與 Array 并沒有繼承關(guān)系明吩,但是它們有同樣的方法屬性集间学,它們也都有相應(yīng)的工廠方法。

val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]
// 大小為 5贺喝、值為 [0, 0, 0, 0, 0] 的整型數(shù)組
val arr = IntArray(5)

// 例如:用常量初始化數(shù)組中的值
// 大小為 5菱鸥、值為 [42, 42, 42, 42, 42] 的整型數(shù)組
val arr = IntArray(5) { 42 }

// 例如:使用 lambda 表達(dá)式初始化數(shù)組中的值
// 大小為 5、值為 [0, 1, 2, 3, 4] 的整型數(shù)組(值初始化為其索引值)
var arr = IntArray(5) { it * 1 }

集合

kotlin中集合分為只讀集合與可變集合躏鱼,如下所示:

集合元素 只讀 可變
List listOf mutableListOf氮采、arrayListOf
Set setOf mutableSetOf、hashSetOf染苛、linkedSetOf鹊漠、sortedSetOf
Map mapOf mutableMapOf、hashMapOf茶行、linkedMapOf躯概、sortedMapOf
  • List是一個有序集合,可通過索引訪問元素畔师。元素可以在 list 中出現(xiàn)多次娶靡。
  • Set是唯一元素的集合,一組無重復(fù)的對象看锉。一般來說 set 中元素的順序并不重要姿锭。例如,字母表是字母的集合(set)伯铣。
  • Map是一組鍵值對呻此。鍵是唯一的,每個鍵都剛好映射到一個值腔寡。值可以重復(fù)

只讀集合的可變性

只讀集合不一定就是不可變的焚鲜。例如,假設(shè)存在一個擁有只讀類型接口的對象放前,該對象存在兩個不同的引用忿磅,一個只讀,一個可變凭语,當(dāng)可變引用修改了該對象后贝乎,這對只讀引用來說就相當(dāng)于“只讀集合被修改了”,因此只讀集合并不總是線程安全的叽粹。如果需要在多線程環(huán)境下處理數(shù)據(jù)览效,需要保證正確地同步了對數(shù)據(jù)的訪問却舀,或者使用支持并發(fā)訪問的數(shù)據(jù)結(jié)構(gòu)

例如,list1 和 list1 引用到同一個集合對象锤灿, list3 對集合的修改同時會影響到 list1

    val list1: List<String> = JavaMain.names
    val list3: MutableList<String> = JavaMain.names
    list1.forEach { it -> println(it) } //leavesC Ye
    list3.forEach { it -> println(it) } //leavesC Ye
    for (index in list3.indices) {
        list3[index] = list3[index].toUpperCase()
    }
    list1.forEach { it -> println(it) } //LEAVESC YE

集合與可空性

集合的可空性可以分為三種:

  1. 可以包含為 null 的集合元素
  2. 集合本身可以為 null
  3. 集合本身可以為 null挽拔,且可以包含為 null 的集合元素

例如,intList1 可以包含為 null 的集合元素但校,但集合本身不能指向 null螃诅;intList2 不可以包含為 null 的集合元素,但集合本身可以指向 null状囱;intList3 可以包含為 null 的集合元素术裸,且集合本身能指向 null

    //List<Int?> 是能持有 Int? 類型值的列表
    val intList1: List<Int?> = listOf(10, 20, 30, 40, null)
    //List<Int>? 是可以為 null 的列表
    var intList2: List<Int>? = listOf(10, 20, 30, 40)
    intList2 = null
    //List<Int?>? 是可以為 null 的列表,且能持有 Int? 類型值
    var intList3: List<Int?>? = listOf(10, 20, 30, 40, null)
    intList3 = null

其他數(shù)據(jù)類型

  • Any:Any 類型是 kotlin 所有非空類型的超類型亭枷,包括像 Int 這樣的基本數(shù)據(jù)類型袭艺,如果把基本數(shù)據(jù)類型的值賦給 Any 類型的變量,則會自動裝箱為java.lang.Integer的叨粘。
  • Any?:Any?類型是 kotlin 所有可空類型的超類型猾编,如果想要使變量可以存儲包括 null 在內(nèi)的所有可能的值,則需要使用 Any?
  • Unit:Unit 類型類似于 Java 中的 void升敲,可以用于函數(shù)沒有返回值時的情況答倡,如果函數(shù)返回值為 Unit,則可以省略該聲明驴党,Unit也可以作為類型參數(shù)來聲明變量瘪撇。
  • Nothing:Nothing 類型沒有任何值,只有被當(dāng)做函數(shù)返回值使用港庄,或者被當(dāng)做泛型函數(shù)返回值的類型參數(shù)使用時才會有意義

函數(shù)

kotlin 中的函數(shù)以關(guān)鍵字 fun 作為開頭倔既,函數(shù)名稱緊隨其后,再之后是用括號包裹起來的參數(shù)列表攘轩,如果函數(shù)有返回值,則再加上返回值類型码俩,用一個冒號與參數(shù)列表隔開度帮。

//fun 用于表示聲明一個函數(shù),double 是函數(shù)名稿存,x表示傳入?yún)?shù)笨篷,Int 表示函數(shù)的返回值類型是int型
fun double(x: Int): Int {
    return 2 * x
}

還有一種是表達(dá)式函數(shù)體,它是用單行表達(dá)式與等號定義的函數(shù)瓣履,表達(dá)式函數(shù)體的返回值類型可以省略率翅,返回值類型可以自動推斷的。如:fun double(x: Int) = x * 2

如果函數(shù)沒有有意義的返回值袖迎,則可以聲明為 Unit 冕臭,也可以省略 Unit腺晾,下面的三種寫法是等價的:

        fun test(str: String, int: Int): Unit {
            println(str.length + int)
        }

        fun test(str: String, int: Int) {
            println(str.length + int)
        }

        fun test(str: String, int: Int) = println(str.length + int)

函數(shù)的參數(shù)

命名參數(shù)

kotlin 允許我們使用命名參數(shù),即在調(diào)用某函數(shù)的時候辜贵,可以將函數(shù)參數(shù)名一起標(biāo)明悯蝉,從而明確地表達(dá)該參數(shù)的含義與作用,但是在指定了一個參數(shù)的名稱后托慨,之后的所有參數(shù)都需要標(biāo)明名稱鼻由。如下所示:

fun main() {
    //該寫法是錯誤的,在指定了一個參數(shù)的名稱后厚棵,之后的所有參數(shù)都需要標(biāo)明名稱
    compute(index = 110, "hello")
    
    compute(index = 120, value = "hello")
    compute(130, value = "hello")
}

fun compute(index: Int, value: String) {

}

默認(rèn)參數(shù)

函數(shù)參數(shù)可以有默認(rèn)值蕉世,當(dāng)省略相應(yīng)的參數(shù)時使用默認(rèn)值。與其他語言相比婆硬,這可以減少重載數(shù)量狠轻。

fun main() {
    compute(24)
    compute(24, "world")
}

fun compute(age: Int, name: String = "hello") {

}

有默認(rèn)參數(shù)時,可以省略的只有排在末尾的參數(shù)柿祈,其他位置的是不能省略的哈误,如下所示:

fun main() {
    //錯誤,不能省略參數(shù) name
    // compute(24)
    // compute(24,100)
    
    //可以省略參數(shù) value
    compute("hello", 24)
}

fun compute(name: String = "hello", age: Int, value: Int = 100) {}

但是躏嚎,如果使用命名參數(shù)蜜自,可以省略任何有默認(rèn)值的參數(shù),而且也可以按照任意順序傳入需要的參數(shù)卢佣。

fun main() {
    compute(age = 24)
    compute(age = 24, name = "hello")
    compute(age = 24, value = 90, name = "hello")
    compute(value = 90, age = 24, name = "hello")
}

fun compute(name: String = "hello", age: Int, value: Int = 100) {

}

可變參數(shù)

kotlin 的語法與 Java 有所不同重荠,可變參數(shù)改為通過使用 varage 關(guān)鍵字聲明

fun main() {
    compute()
    compute("hello")
    compute("hello", "world")
    compute("hello", "world", "kotlin")
}

fun compute(vararg name: String) {
    name.forEach { println(it) }
}

在 Java 中,可以直接將數(shù)組傳遞給可變參數(shù)虚茶,而 kotlin 要求顯式地解包數(shù)組戈鲁,以便每個數(shù)組元素在函數(shù)中能夠作為單獨的參數(shù)來調(diào)用,這個功能被稱為展開運算符嘹叫,使用方式就是在數(shù)組參數(shù)前加一個 *

fun main() {
    val names = arrayOf("hello", "world", "kotlin")
    compute(* names)
}

fun compute(vararg name: String) {
    name.forEach { println(it) }
}

局部函數(shù)

在Kotlin中婆殿,支持在函數(shù)中嵌套函數(shù),被嵌套的函數(shù)稱為局部函數(shù)

fun compute(name: String, country: String) {
    fun check(string: String) {
        if (string.isEmpty()) {
            throw IllegalArgumentException("參數(shù)錯誤")
        }
    }
    check(name)
    check(country)
}

check方法體是放在compute方法體中罩扇,check方法被稱為局部方法或局部函數(shù)婆芦;check只能在compute中方法調(diào)用,在compute方法外調(diào)用喂饥,會引起編譯錯誤消约。

控制流

IF表達(dá)式

在 Kotlin 中,if是一個表達(dá)式员帮,即它會返回一個值或粮。 因此就不需要三元運算符(條件 ? 然后 : 否則),因為普通的 if 就能勝任這個角色捞高,故kotlin中沒有三元運算符氯材。

// 傳統(tǒng)用法
var max = a 
if (a < b) max = b

// With else 
var max: Int
if (a > b) {
    max = a
} else {
    max = b
}
 
// 作為表達(dá)式
val max = if (a > b) a else b

if 的分支可以是代碼塊渣锦,最后的表達(dá)式作為該塊的值

//a,b就是該塊兒最后返回的值
val max = if (a > b) {
    print("Choose a")
    a
} else {
    print("Choose b")
    b
}

如果你使用 if 作為表達(dá)式而不是語句(例如:返回它的值或者把它賦給變量),該表達(dá)式需要有 else 分支

when表達(dá)式

when 表達(dá)式與 Java 中的 switch/case 類似浓体,但功能更為強(qiáng)大泡挺。when 既可以被當(dāng)做表達(dá)式使用也可以被當(dāng)做語句使用,when 將參數(shù)和所有的分支條件順序比較直到某個分支滿足條件命浴,然后它會運行右邊的表達(dá)式娄猫。

如果 when 被當(dāng)做表達(dá)式來使用,符合條件的分支的值就是整個表達(dá)式的值生闲。與 Java 的 switch/case 不同之處是 when 表達(dá)式的參數(shù)可以是任何類型媳溺,并且分支也可以是一個條件。

和 if 一樣碍讯,when 表達(dá)式每一個分支可以是一個代碼塊悬蔽,它的值是代碼塊中最后的表達(dá)式的值,如果其它分支都不滿足條件將會求值于 else 分支捉兴。

如果 when 作為一個表達(dá)式使用蝎困,則必須有 else 分支, 除非編譯器能夠檢測出所有的可能情況都已經(jīng)覆蓋了倍啥。如果很多分支需要用相同的方式處理禾乘,則可以把多個分支條件放在一起,用逗號分隔虽缕。

    val value = 2
    when (value) {
        in 4..9 -> println("in 4..9") //區(qū)間判斷
        3 -> println("value is 3")    //相等性判斷
        2, 6 -> println("value is 2 or 6")    //多值相等性判斷
        is Int -> println("is Int")   //類型判斷
        else -> println("else")       //如果以上條件都不滿足始藕,則執(zhí)行 else
    }

fun main() {
    //返回 when 表達(dá)式
    fun parser(obj: Any): String =
            when (obj) {
                1 -> "value is 1"
                "4" -> "value is string 4"
                is Long -> "value type is long"
                else -> "unknown"
            }
}

when 語句也可以不帶參數(shù)來使用

    when {
        1 > 5 -> println("1 > 5")
        3 > 1 -> println("3 > 1")
    }

For循壞

    //和java中使用方式很類似
    val list = listOf(1, 4, 10, 34, 10)
    for (value in list) {
        println(value)
    }

通過索引來遍歷

    val items = listOf("H", "e", "l", "l", "o")
    //通過索引來遍歷List
    for (index in items.indices) {
        println("${index}對應(yīng)的值是:${items[index]}")
    }

也可以在每次循環(huán)中獲取當(dāng)前索引和相應(yīng)的值

    val list = listOf(1, 4, 10, 34, 10)
    for ((index, value) in list.withIndex()) {
        println("index : $index , value :$value")
    }

也可以自定義循環(huán)區(qū)間

    for (index in 2..10) {
        println(index)
    }

while循環(huán)和do/while循環(huán)

兩者的使用和Java中的使用相似。

    val list = listOf(1, 4, 15, 2, 4, 10, 0, 9)
    var index = 0
    while (index < list.size) {
        println(list[index])
        index++
    }

    val list = listOf(1, 4, 15, 2, 4, 10, 0, 9)
    var index = 0
    do {
        println(list[index])
        index++
    } while (index < list.size)

返回與跳轉(zhuǎn)

Kotlin 有三種結(jié)構(gòu)化跳轉(zhuǎn)表達(dá)式(作用和java語言中的類似):

  • return 默認(rèn)從最直接包圍它的函數(shù)或者匿名函數(shù)返回
  • break 終止最直接包圍它的循環(huán)
  • continue 繼續(xù)下一次最直接包圍它的循環(huán)
    在 kotlin 中任何表達(dá)式都可以用標(biāo)簽(label)來標(biāo)記氮趋,標(biāo)簽的格式為標(biāo)識符后跟 @ 符號伍派,例如:abc@ 、fooBar@ 都是有效的標(biāo)簽
fun main() {
    fun1()
}

fun fun1() {
    val list = listOf(1, 4, 6, 8, 12, 23, 40)
    loop@ for (it in list) {
        if (it == 8) {
            continue
        }
        //當(dāng)值是23的時候剩胁,退出標(biāo)記的loop循環(huán)
        if (it == 23) {
            break@loop
        }
        println("value is $it")
    }
    println("function end")
}

通常情況下使用隱式標(biāo)簽更方便诉植, 隱式標(biāo)簽與接受該 lambda 的函數(shù)同名,return也可添加標(biāo)簽限制(如下:)

fun fun3() {
    val list = listOf(1, 4, 6, 8, 12, 23, 40)
    list.forEach {
        if (it == 8) {
            //這是就是在return上加了隱式標(biāo)簽forEach的限制昵观,使之只在forEach當(dāng)前循環(huán)中終止返回晾腔,效果同continue
            return@forEach
        }
        println("value is $it")
    }
    println("function end")
 //運行fun3方法的話,會輸出以下結(jié)果:
//    value is 1
//    value is 4
//    value is 6
//    value is 12
//    value is 23
//    value is 40
//    function end
}

fun fun4() {
    val list = listOf(1, 4, 6, 8, 12, 23, 40)
    list.forEach loop@{
        if (it == 8) {
            return@loop//同fun3一樣索昂,這里是添加了loop的標(biāo)簽顯式標(biāo)簽
        }
        println("value is $it")
    }
    println("function end")
//運行fun4方法的話建车,會輸出以下結(jié)果:
//    value is 1
//    value is 4
//    value is 6
//    value is 12
//    value is 23
//    value is 40
//    function end
}

fun fun5() {
    listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
        if (value == 3) {
            //局部返回到匿名函數(shù)的調(diào)用者扩借,即 forEach 循環(huán)
            return
        }
        println("value is $value")
    })
    println("function end")
}
//運行fun5方法的話椒惨,會輸出以下結(jié)果:
//value is 1
//value is 2
//value is 4
//value is 5
//function end

區(qū)間

Kotlin 可通過調(diào)用 kotlin.ranges 包中的 [rangeTo()] 函數(shù)及其操作符 .. 形式的輕松地創(chuàng)建兩個值的區(qū)間。 通常潮罪,rangeTo() 會輔以 in!in 函數(shù)

if (i in 1..4) {  // 等同于 i>=1 && i <= 4
    print(i)
}

if (i in 1.rangeTo(4)) {  // 和上面是相同的
    print(i)
}

數(shù)字類型的 ranges 在被迭代時康谆,等同于java中帶索引的fori的循環(huán)的效果

for (i in 1..4) {
    print(i)
}

要反向迭代數(shù)字领斥,請使用 [downTo]函數(shù)而不是 ..rangeTo()。

for (i in 4 downTo 1) print(i)

這是通過 [step]函數(shù)完成任意步長(不一定為 1 )迭代數(shù)字

for (i in 1..8 step 2) print(i)
println()
for (i in 8 downTo 1 step 2) print(i)

以上聲明的都是閉區(qū)間沃暗,如果想聲明的是開區(qū)間)月洛,可以使用 until 函數(shù)

for (i in 1 until 4) {
    println(i)
}

空安全

在 kotlin 中,類型系統(tǒng)將一個引用分為可以容納 null (可空引用)或者不能容納 null(非空引用)兩種類型孽锥。 常規(guī)的變量不能指向null,如果希望一個變量可以儲存 null 引用嚼黔,需要顯式地在類型名稱后面加上問號?

    //name變量不能被賦值為null
    var name: String = "hello"
    
    //name變量可以被賦值為null
    var name: String? = "hello"

kotlin 對可空類型的顯式支持有助于防止 NullPointerException 導(dǎo)致的異常問題惜辑,編譯器不允許不對可空變量做 null 檢查就直接調(diào)用其屬性唬涧。

var name: String? = "hello"

val l = name.length // 編譯器會報錯,因為name變量可能為null

在寫代碼的時候盛撑,我們可以對可空的變量做判空判斷碎节,kotlin也為我們提供了安全的調(diào)用的運算符

安全的調(diào)用

?.:如果變量值非空,則變量的方法或?qū)傩詴徽{(diào)用抵卫,否則直接返回 null狮荔。

//b變量聲明的是可空類型的,當(dāng)b!=null的時候介粘,就返回的b.length的值
//當(dāng)b=null時殖氏,b?.length就自動返回的null,可以看出這個表達(dá)式返回的類型是Int?
println(b?.length)

安全調(diào)用在鏈?zhǔn)秸{(diào)用中也很有用,例如碗短,如果一個員工 Bob 可能會(或者不會)分配給一個部門受葛, 并且可能有另外一個員工是該部門的負(fù)責(zé)人,那么獲取 Bob 所在部門負(fù)責(zé)人(如果有的話)的名字偎谁,我們寫作:bob?.department?.head?.name

如果任意一個屬性(環(huán)節(jié))為空总滩,這個鏈?zhǔn)秸{(diào)用就會返回 null

如果要只對非空值執(zhí)行某個操作巡雨,安全調(diào)用操作符可以與 [let] 一起使用:

val listWithNulls: List<String?> = listOf("Kotlin", null)
for (item in listWithNulls) {
    item?.let { println(it) } // 輸出 Kotlin 并忽略 null
}

安全調(diào)用也可以出現(xiàn)在賦值的左側(cè)闰渔。這樣,如果調(diào)用鏈中的任何一個接收者為空都會跳過賦值铐望,而右側(cè)的表達(dá)式根本不會求值:

// 如果 `person` 或者 `person.department` 其中之一為空冈涧,都不會調(diào)用該函數(shù):
person?.department?.head = managersPool.getManager()

Elvis 操作符

當(dāng)我們有一個可空的引用 b 時,如果 b 非空正蛙,我使用它督弓;如果使用 b 是空的話,我們想使用某個非空的值乒验,如下所示:

val l: Int = if (b != null) b.length else -1

除了可以用if表達(dá)式愚隧,這還可以通過 Elvis 操作符表達(dá),寫作 ?:

//當(dāng)表達(dá)式b?.length為空(即b=null)的時候賦值為-1
val l = b?.length ?: -1

如果 ?: 左側(cè)表達(dá)式非空锻全,elvis 操作符就返回其左側(cè)表達(dá)式狂塘,否則返回右側(cè)表達(dá)式录煤。 請注意,當(dāng)且僅當(dāng)左側(cè)為空時荞胡,才會對右側(cè)表達(dá)式求值妈踊。

安全的類型轉(zhuǎn)換

如果對象不是目標(biāo)類型,那么常規(guī)類型轉(zhuǎn)換可能會導(dǎo)致 ClassCastException泪漂。 另一個選擇是使用安全的類型轉(zhuǎn)換as?廊营,如果嘗試轉(zhuǎn)換不成功則返回 null

//如果a不是Int類型的話,會返回null
val aInt: Int? = a as? Int

非空斷言運算符!!

!!是將任何值轉(zhuǎn)換為非空類型萝勤,若該值為空則拋出異常

//如果b=null赘风,會拋出空指針NPE異常
val l = b!!.length

可空類型的擴(kuò)展

為可空類型定義擴(kuò)展函數(shù)是一種更強(qiáng)大的處理 null 值的方式,可以允許接收者為 null 的調(diào)用纵刘,并在該函數(shù)中處理 null 邀窃,而不是在確保變量不為 null 之后再調(diào)用它的方法

    //可以被正常調(diào)用而不會發(fā)生空指針異常
    val name: String? = null
    println(name.isNullOrEmpty()) //true

類型檢測與類型轉(zhuǎn)換

類型檢查is!is 操作符

在運行時通過使用 is 操作符或其否定形式 !is 來檢測對象是否符合給定類型

if (obj is String) {
    print(obj.length)
}

if (obj !is String) { // 與 !(obj is String) 相同
    print("Not a String")
}
else {
    print(obj.length)
}

智能轉(zhuǎn)換

在許多情況下,不需要在 Kotlin 中使用顯式轉(zhuǎn)換操作符假哎,因為編譯器跟蹤不可變值的 is-檢測以及[顯式轉(zhuǎn)換]瞬捕,在需要時自動插入(安全的)轉(zhuǎn)換:

fun demo(x: Any) {
    if (x is String) {
        print(x.length) // x 自動轉(zhuǎn)換為字符串
    }
}

if (x !is String) return
print(x.length) // x 自動轉(zhuǎn)換為字符串

// `||` 右側(cè)的 x 自動轉(zhuǎn)換為字符串
if (x !is String || x.length == 0) return

// `&&` 右側(cè)的 x 自動轉(zhuǎn)換為字符串
if (x is String && x.length > 0) {
    print(x.length) // x 自動轉(zhuǎn)換為字符串
}

//用在when表達(dá)式和while循環(huán)也是一樣
when (x) {
    is Int -> print(x + 1)
    is String -> print(x.length + 1)
    is IntArray -> print(x.sum())
}

不安全轉(zhuǎn)換操作符

如果轉(zhuǎn)換是不可能的,轉(zhuǎn)換操作符會拋出一個異常舵抹,因此肪虎,它是不安全的。 Kotlin 中的不安全轉(zhuǎn)換由操作符 as完成惧蛹;可空類型和不可空類型是不同的類型扇救,不能轉(zhuǎn)換,比如:StringString?是不同的類型香嗓。

安全轉(zhuǎn)換操作符

為了避免拋出異常迅腔,可以使用安全轉(zhuǎn)換操作符 as? ,它可以在失敗時返回 null

//as?右邊是非空類型的靠娱,聲明是可空類型的沧烈,若用as就會拋出異常,這里使用as?可以返回null像云,所以結(jié)果是可空的
val x: String? = y as? String

作用域函數(shù)

kotlin標(biāo)準(zhǔn)庫包含幾個函數(shù)锌雀,它們的唯一目的是在對象的上下文中執(zhí)行代碼塊。當(dāng)對一個對象調(diào)用這樣的函數(shù)并提供一個lambda表達(dá)式時迅诬,他會形成一個臨時作用域腋逆。在此作用域中,可以訪問該對象而無需其名稱侈贷,這些函數(shù)稱為作用域函數(shù)

這些函數(shù)基本上做了同樣的事情:在一個對象上執(zhí)行一個代碼塊惩歉。不同的是這個對象在塊中如何使用,以及整個表達(dá)式的結(jié)果是什么。

下面是作用域函數(shù)的典型用法:

Person("Alice", 20, "Amsterdam").let {
    println(it)
    it.moveTo("London")
    it.incrementAge()
    println(it)
}

如果不使用 let 來寫這段代碼柬泽,就必須引入一個新變量,并在每次使用它時重復(fù)其名稱嫁蛇。

val alice = Person("Alice", 20, "Amsterdam")
println(alice)
alice.moveTo("London")
alice.incrementAge()
println(alice)

作用域函數(shù)沒有引入任何新的技術(shù)锨并,但是它們可以使你的代碼更加簡潔易讀。

區(qū)別

標(biāo)準(zhǔn)庫中提供了幾個作用域函數(shù)睬棚,他們本質(zhì)上都非常相似第煮,每個作用域函數(shù)之間有兩個主要區(qū)別:

  • 引用上下文對象的方式
  • 返回值

上下文對象:this還是it

this

this關(guān)鍵字可看作為lambda 表達(dá)式的的接收著。run抑党、with 以及 apply 通過關(guān)鍵字 this 引用上下文對象包警,在它們的 lambda 表達(dá)式中可以像在普通的類函數(shù)中一樣訪問上下文對象。在大多數(shù)場景底靠,當(dāng)你訪問接收者對象時你可以省略 this害晦,來讓你的代碼更簡短。

val adam = Person("Adam").apply { 
    age = 20                       // 和 this.age = 20 或者 adam.age = 20 一樣
    city = "London"
}
println(adam)
it

it關(guān)鍵字可看作為作為 lambda 表達(dá)式的參數(shù)暑中。例如letalso 將上下文對象作為 lambda 表達(dá)式參數(shù)壹瘟,如果沒有指定參數(shù)名,對象可以用隱式默認(rèn)名稱 it 訪問

fun getRandomInt(): Int {
    return Random.nextInt(100).also {
        writeToLog("getRandomInt() generated value $it")
    }
}

val i = getRandomInt()

此外鳄逾,當(dāng)將上下文對象作為參數(shù)傳遞時稻轨,可以為上下文對象指定在作用域內(nèi)的自定義名稱。

fun getRandomInt(): Int {
    //自定義名稱value
    return Random.nextInt(100).also { value ->
        writeToLog("getRandomInt() generated value $value")
    }
}

val i = getRandomInt()

返回值

根據(jù)返回結(jié)果雕凹,作用域函數(shù)可以分為以下兩類:

  • applyalso 返回上下文對象殴俱。
  • letrunwith 返回 lambda 表達(dá)式結(jié)果.
返回上下文對象

applyalso 的返回值是上下文對象本身枚抵。

val numberList = mutableListOf<Double>()

//返回的是對象线欲,可以鏈?zhǔn)秸{(diào)用
numberList.also { println("Populating the list") }
    .apply {
        add(2.71)
        add(3.14)
        add(1.0)
    }
    .also { println("Sorting the list") }
    .sort()

當(dāng)然,也還可以用在返回上下文對象的函數(shù)的 return 語句中

fun getRandomInt(): Int {
    return Random.nextInt(100).also {
        writeToLog("getRandomInt() generated value $it")
    }
}

val i = getRandomInt()
返回表達(dá)式結(jié)果

let汽摹、runwith 返回 lambda 表達(dá)式的結(jié)果

val numbers = mutableListOf("one", "two", "three")
val countEndsWithE = numbers.run { 
    add("four")
    add("five")
    count { it.endsWith("e") }//返回的結(jié)果
}
//There are 3 elements that end with e.
println("There are $countEndsWithE elements that end with e.")

此外询筏,還可以忽略返回值,僅使用作用域函數(shù)為變量創(chuàng)建一個臨時作用域竖慧。

val numbers = mutableListOf("one", "two", "three")
with(numbers) {
    val firstItem = first()
    val lastItem = last()        
    println("First item: $firstItem, last item: $lastItem")
}

函數(shù)選擇

前文提到了幾個函數(shù)嫌套,那么怎么選擇合適的作用域函數(shù)呢?圾旨,下面是列舉了它們之間的主要區(qū)別表

函數(shù) 對象引用 返回值 是否是擴(kuò)展函數(shù)
let it Lambda 表達(dá)式結(jié)果
run this Lambda 表達(dá)式結(jié)果
run - Lambda 表達(dá)式結(jié)果 不是:調(diào)用無需上下文對象
with this Lambda 表達(dá)式結(jié)果 不是:把上下文對象當(dāng)做參數(shù)
apply this 上下文對象
also it 上下文對象

一下是對作用域函數(shù)的簡短總結(jié):

  • 對一個非空(non-null)對象執(zhí)行 lambda 表達(dá)式:let
  • 將表達(dá)式作為變量引入為局部作用域中:let
fun main() {
    val nickName = "leavesC"
    val also = nickName.let {
        it.length
    }
    println(also) //7
}
  • 對象配置:apply
val adam = Person("Adam").apply {
    age = 32
    city = "London"        
}
println(adam)
  • 對象配置并且計算結(jié)果:run
val service = MultiportService("https://example.kotlinlang.org", 80)

val result = service.run {
    port = 8080
    query(prepareRequest() + " to port $port")
}
  • 在需要表達(dá)式的地方運行語句:非擴(kuò)展的 run
val hexNumberRegex = run {
    val digits = "0-9"
    val hexDigits = "A-Fa-f"
    val sign = "+-"

    Regex("[$sign]?[$digits$hexDigits]+")
}

for (match in hexNumberRegex.findAll("+1234 -FFFF not-a-number")) {
    println(match.value)
}
  • 附加效果:also
val numbers = mutableListOf("one", "two", "three")
numbers
    .also { println("The list elements before adding new one: $it") }
    .add("four")
  • 一個對象的一組函數(shù)調(diào)用:with
val result = with(StringBuilder()) {
        append("leavesC")
        append("\n")
        for (letter in 'A'..'Z') {
            append(letter)
        }
        toString()
    }
    println(result)

takeIftakeUnless

除了作用域函數(shù)外踱讨,標(biāo)準(zhǔn)庫還包含函數(shù) takeIftakeUnless。這倆函數(shù)使你可以將對象狀態(tài)檢查嵌入到調(diào)用鏈中砍的。
takeIf 接收一個返回值類型為 bool 的函數(shù)痹筛,當(dāng)該參數(shù)返回值為 true 時返回接受者對象本身,否則返回 null。
takeUnless 的判斷條件與 takeIf 相反帚稠。

fun main() {
    println(check("leavesC")) //7
    println(check(null)) //0
}

fun check(name: String?): Int {
    return name.takeIf { !it.isNullOrBlank() }?.length ?: 0
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谣旁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子滋早,更是在濱河造成了極大的恐慌榄审,老刑警劉巖航厚,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幅狮,死亡現(xiàn)場離奇詭異,居然都是意外死亡缎罢,警方通過查閱死者的電腦和手機(jī)昔头,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門饼问,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人揭斧,你說我怎么就攤上這事莱革。” “怎么了讹开?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵驮吱,是天一觀的道長。 經(jīng)常有香客問我萧吠,道長左冬,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任纸型,我火速辦了婚禮拇砰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘狰腌。我一直安慰自己除破,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布琼腔。 她就那樣靜靜地躺著瑰枫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪丹莲。 梳的紋絲不亂的頭發(fā)上光坝,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機(jī)與錄音甥材,去河邊找鬼盯另。 笑死,一個胖子當(dāng)著我的面吹牛洲赵,可吹牛的內(nèi)容都是我干的鸳惯。 我是一名探鬼主播商蕴,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼芝发!你這毒婦竟也來了绪商?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤辅鲸,失蹤者是張志新(化名)和其女友劉穎格郁,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瓢湃,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年赫蛇,在試婚紗的時候發(fā)現(xiàn)自己被綠了绵患。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡悟耘,死狀恐怖落蝙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情暂幼,我是刑警寧澤筏勒,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站旺嬉,受9級特大地震影響管行,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜邪媳,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一捐顷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧雨效,春花似錦迅涮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至据悔,卻和暖如春传透,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背极颓。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工旷祸, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人讼昆。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓托享,卻偏偏與公主長得像骚烧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子闰围,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容