簡(jiǎn)介
Kotlin 是 JetBrains 公司開(kāi)源的一門(mén)現(xiàn)代化靜態(tài)類(lèi)型編程語(yǔ)言邑贴。Kotlin 支持多平臺(tái)操作检眯,可應(yīng)用于 Android 開(kāi)發(fā)羡鸥、JavaScript 開(kāi)發(fā)、服務(wù)端開(kāi)發(fā)以及其他任何運(yùn)行 JVM 的環(huán)境昧港,Kotlin 的多目標(biāo)平臺(tái)實(shí)現(xiàn)原理在于其支持編譯為 Java 字節(jié)碼屁置、JavaScript 字節(jié)碼等目標(biāo)平臺(tái)的字節(jié)碼。
Kotlin 的三大特性是:簡(jiǎn)潔太惠、安全 和 互操作:
-
簡(jiǎn)潔(Concise):Kotlin 編寫(xiě)的代碼非常簡(jiǎn)潔蓝丙,其提供的諸如
data class
级遭、object
關(guān)鍵字、Lambda 表達(dá)式等特性可以顯著減少樣板代碼渺尘。 -
安全(Safe):Kotlin 將所有的類(lèi)型都拆分成兩種:「非空類(lèi)型」和「可空類(lèi)型」挫鸽,這可以有效避免程序運(yùn)行過(guò)程中出現(xiàn)
NullPointerException
,并且 Kotlin 對(duì)可空類(lèi)型也提供了多種便利操作方法鸥跟,另外丢郊,Kotlin 還提供了智能類(lèi)型轉(zhuǎn)換,一次檢查通過(guò)即自動(dòng)轉(zhuǎn)換為檢查類(lèi)型,減少了后續(xù)冗余轉(zhuǎn)換蚂夕。 - 互操作(Interoperable):Kotlin 100% 兼容 Java 語(yǔ)言迅诬,Kotlin 代碼可調(diào)用 Java,Java 代碼可調(diào)用 Kotlin婿牍。
注:Kotlin 語(yǔ)言開(kāi)發(fā)的最初目標(biāo)就是用于替換 Java 語(yǔ)言,因此本文主要基于 JVM 平臺(tái)進(jìn)行講解(即將 Kotlin 代碼編譯為 Java 字節(jié)碼)惩歉。
開(kāi)發(fā)工具
支持 Kotlin 語(yǔ)言編寫(xiě)的開(kāi)發(fā)工具主要有:
- IntelliJ IDEA: IntelliJ IDEA 是 JetBrains 公司開(kāi)發(fā)的等脂,是 Kotlin 官方推薦的開(kāi)發(fā)工具。
- Android Studio:Android Studio 是谷歌公司基于 IntelliJ IDEA 修改的一個(gè)開(kāi)發(fā)工具撑蚌,主要用于 Android 開(kāi)發(fā)上遥。
- Complier:Complier 是一個(gè)命令行編譯工具。
本文采用的開(kāi)發(fā)工具為 IntelliJ IDEA争涌,具體安裝步驟網(wǎng)上查詢(xún)即可粉楚,此處略過(guò)不表。
注:IntelliJ IDEA 中提供了字節(jié)碼查看功能:Tools -> Kotlin -> Show Kotlin Bytecode亮垫,此時(shí)就會(huì)彈出窗體顯示當(dāng)前文件字節(jié)碼模软,在當(dāng)前窗體點(diǎn)擊 Decompile 選項(xiàng),就可將字節(jié)碼反編譯為 Java 代碼饮潦,這樣就可以知道 Kotlin 代碼最終編譯等效的 Java 代碼燃异,非常有用。
編程范式
Kotlin 語(yǔ)言支持的編程范式有:面向?qū)ο?/strong> 和 函數(shù)式继蜡。
類(lèi)型系統(tǒng)
Kotlin 在編譯期間進(jìn)行類(lèi)型檢查回俐,且不支持類(lèi)型間隱式轉(zhuǎn)換,因此 Kotlin 是一門(mén) 強(qiáng)靜態(tài)類(lèi)型語(yǔ)言稀并。
一個(gè)值得提及的點(diǎn)是仅颇,Kotlin 對(duì)其類(lèi)型系統(tǒng)進(jìn)行了類(lèi)型拆分操作,將 Kotlin 中的所有類(lèi)型都拆分為:非空類(lèi)型 和 可空類(lèi)型碘举。其中忘瓦,非空類(lèi)型變量不允許設(shè)置為null
,相對(duì)于 Java 來(lái)說(shuō)殴俱,這在很大程度上規(guī)避了程序在運(yùn)行中可能出現(xiàn)的NullPointerException
政冻,解決了 Java 中被稱(chēng)為可能導(dǎo)致數(shù)十億美元損失的令人討厭的空指針異常。從這點(diǎn)上來(lái)說(shuō)线欲,Kotlin 實(shí)現(xiàn)的類(lèi)型系統(tǒng)相比于 Java 更加安全明场。
Hello World
最簡(jiǎn)入門(mén)示例如下所示:
fun main(args: Array<String>) {
println("Hello World")
}
其中,main
函數(shù)就是 Java 語(yǔ)言中的public static void main(String[] args){...}
注:上述例程還可簡(jiǎn)化為:fun main() = println("Hello World")
注:Kotlin 中每條語(yǔ)句后面無(wú)需使用;
李丰,但是如果同一行中存在多條語(yǔ)句苦锨,則必須使用;
進(jìn)行分隔:
fun main() {
print("Hello ");println("World") // => Hello World
}
數(shù)據(jù)類(lèi)型
在 Kotlin 中,所有的類(lèi)型都是引用類(lèi)型,因此舟舒,變量永遠(yuǎn)指向一個(gè)對(duì)象引用拉庶。
注:基本數(shù)據(jù)類(lèi)型通常為值類(lèi)型數(shù)據(jù),操作效率會(huì)更高效秃励。Kotlin 雖然不存在基本數(shù)據(jù)類(lèi)型氏仗,但在編譯期間會(huì)進(jìn)行優(yōu)化,將一些數(shù)值類(lèi)型夺鲜、字符和布爾值等自動(dòng)拆箱為基本數(shù)據(jù)類(lèi)型皆尔,提高程序運(yùn)行效率。
下面介紹 Kotlin 中常見(jiàn)的一些內(nèi)置類(lèi)型:
-
數(shù)值類(lèi)型:Kotlin 中數(shù)值類(lèi)型使用
Number
表示币励,數(shù)值類(lèi)型包含 整型 和 浮點(diǎn)型:
注:Number
是整型(比如Int
)和浮點(diǎn)型(Flaot
慷蠕、Double
)的基類(lèi)。-
整型:Kotlin 中的整型類(lèi)型如下表所示:
Type Size(bits) Min value Max value Byte
8 -128 127 Short
16 -32768 32767 Int
32 -2,147,483,648 ( )
2,147,483,647 ( )
Long
64 -9,223,372,036,854,775,808 ( )
9,223,372,036,854,775,807 ( )
舉個(gè)例子:
val one = 1 // 默認(rèn)為 Int 類(lèi)型 val threeBillion = 3000000000 // 超過(guò) Int.MAX_VALUE食呻,則為 Long 類(lèi)型 val oneLong = 1L // 使用后綴`L`顯示聲明為 Long 類(lèi)型 val oneByte: Byte = 1 // Byte
-
浮點(diǎn)型:Kotlin 內(nèi)置的浮點(diǎn)型類(lèi)型如下表所示:
Type Size(bits) 有效數(shù)字(Significant bits) 指數(shù)位數(shù)(Exponent bits) 小數(shù)位數(shù)(Decimal digits) Float
32 24 8 6-7 Double
64 53 11 15-16 舉個(gè)例子:
val pi = 3.14 // 默認(rèn)為 Double 類(lèi)型 val e = 3.14f // 后綴使用`f`或`F`顯示聲明為 Float 類(lèi)型 val eFloat = 2.7182818284f // Float 類(lèi)型小數(shù)個(gè)數(shù)超出 6-7 位時(shí)會(huì)舍入流炕,其實(shí)際值為 2.7182817
數(shù)值類(lèi)型的一些注意事項(xiàng)如下:
-
字面常量(Literal constants)表示法:Kotlin 支持十進(jìn)制、十六進(jìn)制和二進(jìn)制字面常量表示:
val a = 123 // 十進(jìn)制表示 val b = 0x0F // 十六進(jìn)制表示 val c = 0b00001011 // 二進(jìn)制表示
注:Kotlin 不支持八進(jìn)制表示法
-
下劃線(xiàn)表示法:Kotlin 支持下劃線(xiàn)數(shù)字常量表示:
val oneMillion = 1_000_000 val creditCardNumber = 1234_5678_9012_3456L val socialSecurityNumber = 999_99_9999L val hexBytes = 0xFF_EC_DE_5E val bytes = 0b11010010_01101001_10010100_10010010
-
底層表示:在 Java 平臺(tái)上仅胞,數(shù)值類(lèi)型會(huì)被存儲(chǔ)為 Java 的基本數(shù)據(jù)類(lèi)型(自動(dòng)拆箱)每辟,除非使用了可空類(lèi)型(比如
Int?
)或者使用了泛型(比如List<Int>
),此時(shí)會(huì)編譯為基本類(lèi)型的包裝類(lèi)型(自動(dòng)裝箱):val a: Int = 100 // 自動(dòng)拆箱為 int val boxedA: Int? = a // Integer(自動(dòng)裝箱) val anotherBoxedA: Int? = a // Integer val b: Int = 10000 // int val boxedB: Int? = b // Integer val anotherBoxedB: Int? = b // Integer println(boxedA === anotherBoxedA) // true饼问,自動(dòng)裝箱緩存機(jī)制 println(boxedB === anotherBoxedB) // false
-
-
-
字符類(lèi)型:在 Kotlin 中影兽,字符類(lèi)型使用
Char
表示,字符由單引號(hào)進(jìn)行包裹:val ch: Char = 'a'
-
布爾類(lèi)型:布爾類(lèi)型使用
Boolean
表示莱革,其有兩個(gè)值:true
和false
:val flag: Boolean = true
-
字符串類(lèi)型:Kotlin 中字符串類(lèi)型使用
String
表示峻堰,Kotlin 支持轉(zhuǎn)義字符串和原義字符串,各自的表示方法如下所示:-
轉(zhuǎn)義字符串:字符串常量使用雙引號(hào)包裹表示:
// \n 會(huì)被轉(zhuǎn)義為換行符 val str: String = "Hello World\n" // => Hello World
-
原義字符串(raw string):原義字符串使用
"""
包裹表示盅视,原義字符串會(huì)保留字符串原始內(nèi)容捐名,包括換行等特殊字符:// \n 保持原義輸出 var rawStr = """Hello World\n""" print(rawStr) // => Hello World\n rawStr = """ Hello World """ print(rawStr) // => Hello // World
注:當(dāng)原義字符串包含換行時(shí),可使用
trimMargin()
函數(shù)去除前導(dǎo)空格闹击,默認(rèn)以|
作為一行起始邊界前綴標(biāo)識(shí)镶蹋,也可以使用自定義邊界前綴,比如:trimMargin(">")
:rawStr = """ |Hello |World """.trimMargin() print(rawStr) // => Hello // World rawStr = """ >Hello >World """.trimMargin(">") print(rawStr) // => Hello // World
String
數(shù)據(jù)是不可修改的(immutable)赏半。常見(jiàn)的字符串操作列舉如下:-
長(zhǎng)度:獲取字符串長(zhǎng)度方法如下:
val str = "Hello Kotlin" // 法一:通過(guò)屬性 length 直接獲取字符串長(zhǎng)度 var len: Int = str.length println(len) // => 12 // 法二:count() 的底層實(shí)現(xiàn)就是直接返回 length 屬性 len = str.count() println(len) // => 12
-
拼接(concat):字符串拼接方法如下:
// 法一:通過(guò) + 號(hào)拼接 var str = "Hello" + "World" println(str) // => Hello World // 法二:通過(guò)操作符 plus() 拼接 str = "Hello".plus("World") println(str) // => Hello World // 法三:字符串模板 str = "Hello" println("${str} World")
-
子串:字符串截取獲取子串方法如下:
val str = "Hello Kotlin" // 法一:通過(guò) subSequence 方法截取 // [startIndex, endIndex)贺归,即 [0, 1) var subStr1: CharSequence = str.subSequence(0,1) println(subStr1) // => H // [0, 1] subStr1 = str.subSequence(IntRange(0,1)) println(subStr1) // => He // 法二:通過(guò) substring 方法截取 // [startIndex, length - 1],即 [0, length -1] var subStr2: String = str.substring(0) println(subStr2) // => Hello Kotlin // [startIndex, endIndex)断箫,即 [0,1) subStr2 = str.substring(0,1) println(subStr2) // => H // [0, 1] subStr2 = str.substring(IntRange(0,1)) println(subStr2) // => He
-
-
數(shù)組類(lèi)型:數(shù)組類(lèi)型使用
Array
表示拂酣。數(shù)組的常見(jiàn)操作如下所示:-
創(chuàng)建:創(chuàng)建數(shù)組主要有如下幾種方式:
// 法一:通過(guò)構(gòu)造函數(shù)創(chuàng)建數(shù)組 val array1: Array = Array(3) { i -> i } // => [0, 1, 2] // 法二:創(chuàng)建并初始化數(shù)組元素 val array2: Array = arrayOf(1, 2, 3) // => [1, 2, 3] // 法三:創(chuàng)建指定長(zhǎng)度數(shù)組,且初始化為空 val array3: Array<Int> = arrayOfNulls<Int>(3) // => [null, null, null] // 法四:創(chuàng)建一個(gè)空數(shù)組 val array4: Array<String> = emptyArray<String>() // => []
以上所有方法創(chuàng)建的都是 Kotlin 內(nèi)置的
Array
類(lèi)型數(shù)組仲义,Kotlin 也支持創(chuàng)建平臺(tái)原生類(lèi)型數(shù)組(避免自動(dòng)拆箱)婶熬,其內(nèi)置了一些數(shù)組類(lèi)來(lái)表示平臺(tái)原生數(shù)組:ByteArray
剑勾、ShortArray
、IntArray
等等赵颅,每種原生數(shù)組都提供相應(yīng)的工廠(chǎng)方法方便創(chuàng)建虽另,比如:intArrayOf()
、longArrayOf()
...:// 通過(guò)構(gòu)造函數(shù)創(chuàng)建原生數(shù)組饺谬,即 int[] var primitiveArray: IntArray = IntArray(3) // => [0, 0, 0] primitiveArray = IntArray(3) { 4 } // => [4, 4, 4] primitiveArray = IntArray(3) { i -> i } // => [0, 1, 2] // 通過(guò)工廠(chǎng)方法創(chuàng)建原生數(shù)組 primitiveArray = intArrayOf(1, 2, 3) // => [1, 2, 3]
-
增/改:數(shù)組設(shè)置或修改元素內(nèi)容方法如下:
val array = arrayOfNulls<String>(2) // 通過(guò) set 方法設(shè)置 array.set(0,"value0") // 通過(guò)操作符 [] 設(shè)置元素 array[1] = "value1" array.forEach { println(it) }
注:
Array
重載了操作符[]
捂刺,使用該操作符可設(shè)置和獲取相應(yīng)索引元素內(nèi)容。 -
查:對(duì)數(shù)組的常見(jiàn)查詢(xún)操作大致有如下幾種:
-
查詢(xún)?cè)貎?nèi)容:獲取數(shù)組相應(yīng)索引元素內(nèi)容方法如下:
val array = arrayOf(1, 2) // 通過(guò) get(index) 方法獲取 val idx0 = array.get(0) // 通過(guò)操作符 [] 獲取 val idx1 = array[1] println("array[0] = $idx0, array[1] = $idx1")
-
遍歷:常使用
for
表達(dá)式或forEach
方法對(duì)數(shù)據(jù)進(jìn)行遍歷募寨,大致有如下幾種格式:val array = intArrayOf(1, 2, 3) // 格式一: 遍歷數(shù)據(jù)內(nèi)容 // for 表達(dá)式 for (item in array) { println(item) } // forEach 方法 array.forEach { item -> println(item) } // 可以使用方法引用進(jìn)行簡(jiǎn)化 array.forEach(::println) // 格式二:遍歷元素索引和內(nèi)容 // for 表達(dá)式 for ((index, item) in array.withIndex()) { println("array[$index] = $item") } // forEach 方法 array.forEachIndexed{ index, item -> println("array[$index] = $item")}
-
-
-
Any
:表示 Kotlin 中所有非空類(lèi)型的基類(lèi):val obj1: String = "test superclass" println(obj1 is Any) // => true // 可空類(lèi)型不為 null 也為 Any 的子類(lèi) val obj2: Int? = 2 println(obj2 is Any) // => true val obj3: Int? = null println(obj3 is Any) // => false println(obj3 is Any?) // => true
注:
Any
即相當(dāng)于 Java 中的Object
類(lèi)
注:依據(jù)類(lèi)型拆分機(jī)制叠萍,Any
的可空類(lèi)型為Any?
,且Any
的超類(lèi)為Any?
:Any() is Any?
為true
-
Unit
:當(dāng)函數(shù)沒(méi)有返回值時(shí)绪商,使用Unit
進(jìn)行修飾:fun returnNothing(): Unit{ println("This function returns void") }
注:當(dāng)函數(shù)沒(méi)有返回值時(shí),通常無(wú)需顯示聲明
Unit
辅鲸,而是直接忽略不寫(xiě)
注:Unit
本質(zhì)是一個(gè)全局單例object
實(shí)例對(duì)象格郁,所以它是一個(gè)Any
類(lèi)型數(shù)據(jù),它的效果等同于 Java 語(yǔ)言的void
關(guān)鍵字:public object Unit { override fun toString() = "kotlin.Unit" }
-
Nothing
:表示一個(gè)永遠(yuǎn)不存在的值独悴,可認(rèn)為是 空類(lèi)型例书。其源碼實(shí)現(xiàn)如下:package kotlin /** * Nothing has no instances. You can use Nothing to represent "a value that never exists": for example, * if a function has the return type of Nothing, it means that it never returns (always throws an exception). */ public class Nothing private constructor()
可以看到,
Nothing
的構(gòu)造函數(shù)是私有的刻炒,因此無(wú)法實(shí)例化决采,實(shí)際上,在程序運(yùn)行期間坟奥,也不會(huì)存在任何Nothing
實(shí)例树瞭,它更多的只是表示一個(gè)編譯期抽象概念,即沒(méi)有值或運(yùn)行異常爱谁。Nothing
的主要作用有如下幾種:-
當(dāng)函數(shù)返回值類(lèi)型聲明為
Nothing
時(shí)晒喷,表示該函數(shù)永遠(yuǎn)不會(huì)正常終止,通常表現(xiàn)為函數(shù)運(yùn)行時(shí)永遠(yuǎn)會(huì)拋出異常:fun fail(msg: String): Nothing { throw RuntimeException(msg) }
Nothing
的這個(gè)特性通常有兩方面用途:-
由于
Nothing
函數(shù)不會(huì)正常終止访敌,因此如果能執(zhí)行到調(diào)用Nothing
函數(shù)凉敲,那么其后面的代碼就不會(huì)被執(zhí)行到,這些代碼可被優(yōu)化掉:val addr = company.address ?: fail("No address") println(addr.city)
如果
company.address
為null
寺旺,那么后續(xù)代碼就不會(huì)被執(zhí)行爷抓,因?yàn)?code>fail會(huì)拋出異常,另外阻塑,只要addr
成功返回蓝撇,那么編譯器會(huì)把addr
的類(lèi)型推斷非空,因?yàn)槿绻麨榭眨?code>fail函數(shù)就終止程序了,這里一個(gè)優(yōu)化的操作就是疼阔,后續(xù)使用addr
不用進(jìn)行判空操作。 -
通常使用
Nothing
的這個(gè)特性用來(lái)聲明一些暫未完成的函數(shù):@kotlin.internal.InlineOnly public inline fun TODO(): Nothing = throw NotImplementedError()
注:
Nothing
與Unit
的區(qū)別在于:Unit
是一個(gè)真正的實(shí)例對(duì)象艾杏,其類(lèi)型為Any
耘沼,只是它表示返回值內(nèi)容為空极颓,但我們?nèi)匀荒芡ㄟ^(guò)val ret = funcReturnsUnit()
來(lái)獲取函數(shù)返回值,而Nothing
表示函數(shù)拋出異常群嗤,沒(méi)有返回值菠隆。 -
-
Nothing?
是所有可空類(lèi)型的子類(lèi)型,且Nothing?
允許的有且只有唯一的值為null
狂秘,所以其可作為其他任何可空類(lèi)型的空引用骇径。從這段話(huà)又可以延伸出如下內(nèi)容:-
null
的類(lèi)型是Nothing?
,Nothing?
是其他可空類(lèi)型的子類(lèi)型者春,這也就是為什么其他可空類(lèi)型的值可以設(shè)為null
-
Nothing
是所有類(lèi)型(可空+非空)的子類(lèi)型破衔,因?yàn)?code>Nothing是Nothing?
的子類(lèi)型,而Nothing?
是所有可空類(lèi)型的子類(lèi)型钱烟,所以Nothing
還可用于作為泛型參數(shù)晰筛,比如:List<Nothing>
-
-
最后,Kotlin 的類(lèi)型系統(tǒng)中拴袭,除了將數(shù)據(jù)類(lèi)型分為可空和非空類(lèi)型外读第,還引入了一種 平臺(tái)類(lèi)型:平臺(tái)類(lèi)型主要是為 Java 引入的,因?yàn)樵?Java 中拥刻,所有對(duì)象均為可空類(lèi)型怜瞒,這樣當(dāng)在 Kotlin 中引用 Java 變量時(shí),就會(huì)存在很多判空操作般哼,相對(duì)繁瑣吴汪,而如果將 Java 變量看成非空類(lèi)型,那么運(yùn)行時(shí)就可能導(dǎo)致NullPointerException
異常逝她,因此浇坐,Kotlin 為了平衡對(duì) Java 變量的操作,引入了平臺(tái)類(lèi)型黔宛,簡(jiǎn)單說(shuō)就是:Kotlin 在引入 Java 變量時(shí)近刘,即可以將其作為可空類(lèi)型,也可以將其作為非空類(lèi)型臀晃,由開(kāi)發(fā)者自行決定觉渴,以平衡判空操作與空指針異常。
變量
格式
Kotlin 中變量的聲明與定義格式如下所示:
[可見(jiàn)性修飾符] {var | val} 名稱(chēng) [: 類(lèi)型] [=] [值]
其中徽惋,Kotlin 中的類(lèi)案淋、對(duì)象、接口险绘、構(gòu)造函數(shù)踢京、方法誉碴、屬性和它們的setter
都可以有 可見(jiàn)性修飾符,Kotlin 中支持如下四種可見(jiàn)性修飾符:
-
public
:表示所有類(lèi)可見(jiàn)瓣距。缺省時(shí)默認(rèn)可見(jiàn)性修飾符為public
-
private
:當(dāng)前類(lèi)可見(jiàn)或當(dāng)前文件內(nèi)可見(jiàn) -
protected
:當(dāng)前類(lèi)及其子類(lèi)均可見(jiàn) -
internal
:同一模塊中可見(jiàn)
變量類(lèi)型
Kotlin 中有三種變量類(lèi)型:
可變變量:表示可以更改變量指向黔帕。
可變變量使用var
關(guān)鍵字進(jìn)行表示:
kotlin var a: Int = 1 // 可變變量可更改指向 a = 2
-
常量變量:表示變量指向無(wú)法更改。
常量變量使用val
進(jìn)行表示:
kotlin val constRef: Int = 1 // 常量變量無(wú)法更改指向 constRef = 2 // error
**注**:`val`變量相當(dāng)于 Java 語(yǔ)言中的`final`變量
-
編譯期常量:編譯期常量使用
const
修飾:const val CONST_STRING = "Hello World"
注:編譯期常量有幾個(gè)使用限制:
-
const
必須作用于頂層作用域蹈丸,或者object
成員變量成黄,或者companion object
成員變量。 -
const
必須以String
類(lèi)型或基本數(shù)據(jù)類(lèi)型值進(jìn)行初始化逻杖。 -
const
不能設(shè)置自定義getter
訪(fǎng)問(wèn)器奋岁。
-
建議優(yōu)先使用val
聲明變量,無(wú)法滿(mǎn)足時(shí)再改為var
荸百,這樣可以避免不經(jīng)意的數(shù)據(jù)修改闻伶,增加程序健壯性。
延遲初始化
Kotlin 支持對(duì)變量的延遲初始化够话,也即變量的懶加載虾攻,主要有如下兩種:
-
lateinit
:lateinit
可以對(duì)非空類(lèi)型var
變量進(jìn)行延遲初始化。通常情況下更鲁,非空類(lèi)型成員變量必須在構(gòu)造器中進(jìn)行初始化,這種設(shè)定限制了程序編寫(xiě)靈活性奇钞,因此 Kotlin 提供了一個(gè)關(guān)鍵字
lateinit
澡为,使能非空成員變量的延遲初始化:private lateinit var a: String fun main() { a = "lazy load variable" }
注:
lateinit
使用有如下幾個(gè)注意事項(xiàng):-
lalteinit
只能作用于非空類(lèi)型變量,并且該變量不能是基本數(shù)據(jù)類(lèi)型景埃。 -
lateinit
也可以作用于類(lèi)成員屬性媒至,但只能作用于類(lèi)中var
成員變量,且要求該成員變量不能位于主構(gòu)造函數(shù)中谷徙,不能有自定義訪(fǎng)問(wèn)器拒啰。
從 Kotlin 1.2 開(kāi)始,lateinit
也支持頂層屬性和局部變量的延遲初始化完慧。
-
-
by lazy()
:by lazy()
用于懶加載val
變量谋旦,且對(duì)任意類(lèi)型數(shù)據(jù)均可懶加載。lazy()
是一個(gè)參數(shù)為 Lambda屈尼,返回值為Lazy<T>
的高階函數(shù)册着,其返回值Lazy<T>
是val
變量或類(lèi)屬性的延遲初始化委托對(duì)象,被lazy()
委托的變量或?qū)傩栽诘谝淮卧L(fǎng)問(wèn)時(shí)脾歧,會(huì)觸發(fā)lazy()
參數(shù) Lambda 執(zhí)行甲捏,該 Lambda 的最后一個(gè)語(yǔ)句作為返回值,賦值給被修飾的變量或?qū)傩员拗矗罄m(xù)訪(fǎng)問(wèn)該變量不會(huì)再觸發(fā) Lambda司顿,因?yàn)樽兞恳殉跏蓟虞d并賦值)成功:private val b: Int? by lazy { println("initialized variable only when first access") 8 // This is the return value } fun main() { println(b) // => initialized variable only when first access // => 8 println(b) // => 8 }
函數(shù)
函數(shù)的聲明與定義格式如下所示:
[可見(jiàn)性修飾符] fun 函數(shù)名稱(chēng) (參數(shù)1: 類(lèi)型, 參數(shù)2: 類(lèi)型...)[: 返回值類(lèi)型] {
函數(shù)體
}
注:函數(shù)默認(rèn)返回類(lèi)型為Unit
芒粹,此時(shí)可忽略函數(shù)返回值類(lèi)型
Kotlin 中支持如下幾種類(lèi)型函數(shù):
-
普通函數(shù):普通類(lèi)型函數(shù)函數(shù),也即頂層函數(shù)化漆,可單獨(dú)定義在一個(gè)文件中。
舉個(gè)例子:定義一個(gè)函數(shù)
add
猎提,參數(shù)接收兩個(gè)Int
類(lèi)型數(shù)據(jù)获三,返回參數(shù)之和:fun add(a: Int, b: Int): Int { return a + b }
Kotlin 函數(shù)調(diào)用大概有如下幾種方式:
法一:按參數(shù)順序進(jìn)行調(diào)用 add(1, 3) 法二:指定命名參數(shù),此時(shí)參數(shù)可任意順序 add( b = 3, a = 1) 法三:也可以?xún)烧呋煊孟撬眨藭r(shí)要遵循函數(shù)參數(shù)順序 add(1, b = 3)
注:命名參數(shù)無(wú)法作用于 Java 函數(shù)疙教,因?yàn)樵?Java8 之前,字節(jié)碼文件不存儲(chǔ)參數(shù)名信息伞租,而 Kotlin 最低兼容 Java6贞谓。
Kotlin 函數(shù)支持 默認(rèn)參數(shù) 和 可變參數(shù):
-
默認(rèn)參數(shù): 可以為函數(shù)參數(shù)設(shè)置一個(gè)默認(rèn)值。當(dāng)調(diào)用函數(shù)時(shí)葵诈,未指定默認(rèn)參數(shù)具體值時(shí)裸弦,則使用默認(rèn)參數(shù)值。
舉個(gè)例子:比如將函數(shù)
add
的第二個(gè)參數(shù)設(shè)置默認(rèn)值為10
:fun add(a: Int, b: Int = 10): Int { return a + b } // 調(diào)用 add(1) // => add(1, 10) add(1,3) // => add(1, 3) // 支持命名參數(shù)調(diào)用 add( a = 1) // => add(1, 10)
注:當(dāng)重載函數(shù)時(shí)作喘,默認(rèn)參數(shù)會(huì)自動(dòng)被忽略(即重載函數(shù)該默認(rèn)參數(shù)降級(jí)為普通參數(shù))
注:默認(rèn)參數(shù)相當(dāng)于 Java 中的函數(shù)重載理疙,可為 Kotlin 默認(rèn)參數(shù)函數(shù)增加@JvmOverloads
注解,這樣編譯器在編譯時(shí)泞坦,會(huì)生成相應(yīng)的 Java 重載函數(shù)窖贤,方便 Java 進(jìn)行調(diào)用。 -
可變參數(shù):Kotlin 支持可變長(zhǎng)度參數(shù)贰锁,可變參數(shù)使用關(guān)鍵字
vararg
表示赃梧。舉個(gè)例子:定義一個(gè)函數(shù)
add
,其支持長(zhǎng)度可變的參數(shù)個(gè)數(shù)豌熄,要求返回所有參數(shù)之和:fun add(vararg args: Int): Int { return args.reduce { a, b -> a + b } } // 調(diào)用 add(1) // => 1 add(1, 2) // => 3 add(1, 2, 3) // => 6
注:Kotlin 中的可變參數(shù)與 Java 類(lèi)似授嘀,其底層實(shí)現(xiàn)都是基于數(shù)組(比如:
Array<out T>
、IntArray
...)锣险,但與 Java 不同的是蹄皱,在 Kotlin 中,如果傳入的是一個(gè)數(shù)組芯肤,不同于 Java 可直接傳入數(shù)組對(duì)象夯接,Kotlin 必須使用 展開(kāi)運(yùn)算符 顯示展開(kāi)數(shù)組,具體為在數(shù)組前添加符號(hào)*
作為展開(kāi)運(yùn)算符:val array = arrayOf(1, 2, 3) add(*array)
-
-
成員函數(shù):其定義格式與普通函數(shù)一致纷妆,只是成員函數(shù)定義在類(lèi)中盔几,作為對(duì)象的一個(gè)方法:
class MyClass { fun add(a: Int, b: Int): Int { return a + b } }
-
單表達(dá)式函數(shù):當(dāng)函數(shù)體只有一條表達(dá)式時(shí),此時(shí)可省略函數(shù)體:
// 函數(shù)體只有一條表達(dá)式:a + b fun add(a: Int, b: Int): Int = a + b
另外掩幢,當(dāng)編譯器能直接推導(dǎo)出函數(shù)返回類(lèi)型時(shí)逊拍,則單表達(dá)式函數(shù)可忽略函數(shù)返回類(lèi)型:
// 編譯期自動(dòng)推導(dǎo)出函數(shù)返回類(lèi)型為 Int fun add(a: Int, b: Int) = a + b
-
局部函數(shù):Kotlin 支持局部函數(shù)定義上鞠,局部函數(shù)可直接使用外部函數(shù)的參數(shù)和局部變量,因此 Kotlin 支持 閉包:
fun outter() { val msg = "Hello Kotlin" // 局部函數(shù)定義 fun localFunc() { println("This is local function") println(msg) // 使用外部局部變量 } // 調(diào)用局部函數(shù) localFunc() }
注:局部函數(shù)不能聲明為內(nèi)聯(lián)函數(shù)芯丧,且擁有局部函數(shù)的函數(shù)也不能聲明為內(nèi)聯(lián)函數(shù)芍阎。
-
擴(kuò)展函數(shù):擴(kuò)展函數(shù)是 Kotlin 中提供的一個(gè)非常有用的功能,它可以為已存在的類(lèi)動(dòng)態(tài)增加成員函數(shù)缨恒。
舉個(gè)例子:為類(lèi)
String
增加一個(gè)成員函數(shù)println
谴咸,用于輸出字符串內(nèi)容:// StringUtil.kt fun String.println() { // 擴(kuò)展函數(shù)中,this 表示類(lèi)型對(duì)象 println(this.toString()) } // 調(diào)用擴(kuò)展函數(shù) "Hello Kotlin".println() // => Hello Kotlin
注:擴(kuò)展函數(shù)基本等同于類(lèi)成員函數(shù)骗露,但擴(kuò)展函數(shù)不能訪(fǎng)問(wèn)
private
和protected
屬性岭佳。注:擴(kuò)展函數(shù)的本質(zhì)是一個(gè)靜態(tài)函數(shù),類(lèi)名為定義擴(kuò)展函數(shù)的文件名萧锉,參數(shù)為調(diào)用對(duì)象珊随,因此上述擴(kuò)展函數(shù)
String.println()
其實(shí)最終編譯為的等效 Java 代碼如下所示:public class StringUtilKt { public static void println(String self) { System.out.println(self.toString()); } public static void main(String[] args) { StringUtilKt.println("Hello Kotlin"); } }
注:Kotlin 同時(shí)也支持為類(lèi)增加 擴(kuò)展屬性:
var StringBuilder.lastChar: Char get() = this.get(length - 1) set(value: Char) { this.setChar(this.length - 1, value) }
-
泛型函數(shù):泛型函數(shù)的定義如下所示:
fun <T> singletonList(item: T): List<T> { /*...*/ }
-
高階函數(shù)(Higher-Order Function):所謂高階函數(shù),即支持將函數(shù)作為參數(shù)或返回一個(gè)函數(shù)的函數(shù)柿隙。Kotlin 支持函數(shù)式編程范式叶洞,因此,在 Kotlin 中禀崖,函數(shù)是一等對(duì)象衩辟,每個(gè)函數(shù)都屬于一種函數(shù)類(lèi)型,比如:
fun add(a: Int, b: Int): Int {...}
上述函數(shù)
add
的類(lèi)型為:(Int, Int) -> Int
波附,即參數(shù)為Int
和Int
惭婿,返回值為Int
類(lèi)型的函數(shù)。我們可以使用變量指向該函數(shù):
// 指向 val funcAdd: (Int, Int) -> Int = ::add // 調(diào)用 funcAdd(1,3)
所以叶雹,定義一個(gè)高階函數(shù),其實(shí)很簡(jiǎn)單换吧,只需參數(shù)設(shè)置為某個(gè)函數(shù)類(lèi)型或返回某個(gè)函數(shù)類(lèi)型即可:
舉個(gè)例子:定義一個(gè)沒(méi)有返回值的函數(shù)
add
折晦,該函數(shù)對(duì)前兩個(gè)參數(shù)進(jìn)行相加操作,然后將結(jié)果傳遞給第三個(gè)回調(diào)函數(shù)參數(shù):// 參數(shù)為函數(shù) fun add(a: Int, b: Int, callback: (sum: Int) -> Unit) { val sum = a + b callback(sum) }
-
Lambda 表達(dá)式(Lambda Expression):Lambda 表達(dá)式是一種函數(shù)字面量(function literals)沾瓦,其將函數(shù)作為一種值來(lái)對(duì)待满着,這樣我們?cè)诰帉?xiě)代碼時(shí),就無(wú)需創(chuàng)建一個(gè)對(duì)應(yīng)類(lèi)的實(shí)例或聲明一個(gè)函數(shù)贯莺,而是直接傳遞函數(shù)即可风喇。
Kotlin 中,Lambda 表達(dá)式由一對(duì)大括號(hào)進(jìn)行包裹缕探,其格式如下所示:
{ 參數(shù)1: 類(lèi)型, 參數(shù)2: 類(lèi)型... -> 函數(shù)體}
注:Lambda 表達(dá)式的返回值為其函數(shù)體最后一條語(yǔ)句的返回值
舉個(gè)例子:比如魂莫,對(duì)于上文高階函數(shù)中的
add
函數(shù),其第三個(gè)參數(shù)為一個(gè)函數(shù)類(lèi)型數(shù)據(jù)爹耗,通常我們都直接傳入一個(gè) Lambda 表達(dá)式耙考,代碼調(diào)用更加簡(jiǎn)潔:add(1, 2, { sum -> println(sum) }) // => 3
當(dāng)函數(shù)的最后一個(gè)參數(shù)為 lambda 表達(dá)式時(shí)谜喊,則可將 lambda 表達(dá)式置于函數(shù)括號(hào)外面:
add(1, 2) { sum -> println(sum) }
注:如果函數(shù)只有一個(gè)參數(shù),且該參數(shù)為 Lambda 表達(dá)式倦始,則函數(shù)括號(hào)可省略斗遏,比如:
singleArgFunc({...})
可簡(jiǎn)寫(xiě)為singleArgFunc {...}
當(dāng) Lambda 表達(dá)式只有一個(gè)參數(shù)時(shí),我們可忽略這個(gè)參數(shù)鞋邑,而在函數(shù)體中直接使用內(nèi)置變量
it
獲取該參數(shù):add(1, 2) { println(it) }
-
匿名函數(shù)(Anonymous function):Kotlin 支持匿名函數(shù)诵次,其定義方式與普通函數(shù)定義一致,只是忽略函數(shù)名稱(chēng):
val anonymousFunc = fun(a: Int, b: Int): Int { return a + b } // 調(diào)用 anonymousFunc(1,2) // => 3
Lambda 表達(dá)式無(wú)法顯示指定函數(shù)返回類(lèi)型枚碗,但絕大多數(shù)情況下逾一,編譯器都能自動(dòng)推斷函數(shù)返回類(lèi)型,所以并沒(méi)有什么必要性视译,但是如果一定需要顯示指定函數(shù)返回值類(lèi)型嬉荆,此時(shí)可選擇使用匿名函數(shù)。
-
內(nèi)聯(lián)函數(shù):Kotlin 中內(nèi)聯(lián)函數(shù)使用
inline
修飾符聲明酷含,內(nèi)聯(lián)函數(shù)消除 Lambda 表達(dá)式帶來(lái)的運(yùn)行時(shí)開(kāi)銷(xiāo)鄙早。inline
函數(shù)調(diào)用時(shí),存在一定的運(yùn)行開(kāi)銷(xiāo)椅亚,對(duì)于大量微小的函數(shù)片段限番,通常將其設(shè)置為內(nèi)聯(lián),將函數(shù)打平呀舔,即編譯時(shí)將內(nèi)聯(lián)函數(shù)內(nèi)部代碼直接嵌入到調(diào)用處弥虐,消除函數(shù)調(diào)用開(kāi)銷(xiāo)。
注:實(shí)際上媚赖,Kotlin 會(huì)將 Lambda 表達(dá)式編譯為某種函數(shù)類(lèi)型的匿名類(lèi)對(duì)象實(shí)例霜瘪,實(shí)際運(yùn)行的是該匿名類(lèi)實(shí)例對(duì)象的方法,因此實(shí)際上惧磺,運(yùn)行 Lambda 表達(dá)式颖对,不僅僅存在函數(shù)調(diào)用開(kāi)銷(xiāo),還存在對(duì)象實(shí)例創(chuàng)建開(kāi)銷(xiāo)磨隘。舉個(gè)例子:比如對(duì)于我們上述高階函數(shù)
add
缤底,我們可將第三個(gè)函數(shù)參數(shù)callback
設(shè)置為內(nèi)聯(lián)函數(shù),消除callback
調(diào)用開(kāi)銷(xiāo):// 將函數(shù) callback 設(shè)置為內(nèi)聯(lián)函數(shù)番捂,消除其函數(shù)調(diào)用開(kāi)銷(xiāo) inline fun callback(sum: Int) = println(sum) fun main() { add(1,2,::callback) }
上述效果其實(shí)就相當(dāng)于如下代碼:
fun add(a: Int, b: Int) { val sum = a + b // callback(sum) println(sum) // 內(nèi)聯(lián)函數(shù)體 }
當(dāng)內(nèi)聯(lián)函數(shù)的參數(shù)為 Lambda 表達(dá)式時(shí)个唧,該 Lambda 表達(dá)式通常也會(huì)被內(nèi)聯(lián):
inline fun add(a: Int, b: Int, callback: (sum: Int) -> Unit) { val sum = a + b callback(sum) }
上述代碼中,
add
為內(nèi)聯(lián)函數(shù)设预,此時(shí)徙歼,callback
也會(huì)自動(dòng)被設(shè)置為內(nèi)聯(lián)。
注:并不是所有的 Lambda 參數(shù)都會(huì)被內(nèi)聯(lián),比如如果 Lambda 參數(shù)在某個(gè)地方被保存起來(lái)鲁沥,那么該 Lambda 參數(shù)將無(wú)法被內(nèi)聯(lián)呼股。一般來(lái)說(shuō),參數(shù)如果被直接調(diào)用或者作為參數(shù)傳遞給另一個(gè)inline
函數(shù)(內(nèi)聯(lián)函數(shù)的 Lambda 參數(shù)不能傳遞給其他非inline
函數(shù)画恰,因?yàn)?Lambda 會(huì)被打平彭谁,嵌入到調(diào)用處,以代碼塊形式存在允扇,消除了其函數(shù)類(lèi)型缠局,故不能被引用),則該 Lambda 參數(shù)是可以被內(nèi)聯(lián)的考润,否則編譯器將給出錯(cuò)誤狭园。noinline
如果我們想禁止函數(shù)內(nèi)聯(lián),保持函數(shù)類(lèi)型糊治,那么可以為函數(shù)添加
noinline
修飾符:
注:通常如果高階函數(shù)內(nèi)唱矛,有一個(gè)非inline
高階函數(shù)也要引用該 Lambda 表達(dá)式,那么就需要將高階函數(shù)的 Lambda 參數(shù)設(shè)置為noinline
井辜,保持函數(shù)類(lèi)型绎谦。inline fun add(a: Int, b: Int, noinline callback: (sum: Int) -> Unit) { val sum = a + b callback(sum) }
此時(shí),
callback
函數(shù)不會(huì)被內(nèi)聯(lián)粥脚。crossline
另外窃肠,默認(rèn)情況下,Lambda 函數(shù)不支持 非局部返回刷允,即不能直接在 Lambda 函數(shù)中直接
return
冤留,如下例子所示:fun highOrderFunc(lambda: () -> Unit) { lambda() } fun main() { highOrderFunc { // return // => error,非局部返回树灶,直接退出 main 函數(shù) return@highOrderFunc // 支持纤怒,局部返回,退出 highOrderFunc 函數(shù)天通,main 函數(shù)后續(xù)代碼可繼續(xù)執(zhí)行 } println("main end") // => main end }
但是如果 Lambda 函數(shù)為內(nèi)聯(lián)函數(shù)泊窘,依據(jù)內(nèi)聯(lián)的運(yùn)作方式,此時(shí)相當(dāng)于直接將 Lambda 函數(shù)體嵌入到調(diào)用處土砂,則此時(shí)支持非局部返回,如下所示:
inline fun highOrderFunc(lambda: () -> Unit) { lambda() } fun main() { highOrderFunc { return // 非局部返回谜洽,成功萝映,直接退出 main 函數(shù) } println("main end") // 不被執(zhí)行 }
上述代碼實(shí)際等價(jià)于下面內(nèi)容:
fun main() { return println("main end") }
有些時(shí)候可能需要禁止非局部返回操作,比如高階函數(shù)中可能創(chuàng)建了其他嵌套函數(shù)或局部對(duì)象阐虚,在這些不同的上下文中序臂,是不能調(diào)用具備非局部返回的內(nèi)聯(lián)函數(shù)的,否則可能意外打斷調(diào)用者本身流程:
inline fun highOrderFunc(lambda: () -> Int) { val runnable = Runnable { lambda() } // => error }
此時(shí),只需為 Lambda 表達(dá)式增加
crossinline
修飾符奥秆,就可禁止該 Lambda 表達(dá)式非局部返回:// 禁止 lambda 函數(shù)非局部返回 inline fun highOrderFunc(crossinline lambda: () -> Int) { val runnable = Runnable { lambda() } } fun main() { highOrderFunc { // return // => error } }
reified
最后逊彭,內(nèi)聯(lián)函數(shù)還支持一個(gè)特別有用的特性:具體化的類(lèi)型參數(shù),即可以在運(yùn)行時(shí)獲取泛型具體類(lèi)型构订。
我們知道侮叮,Java 中存在泛型類(lèi)型擦除問(wèn)題,也就是我們無(wú)法在運(yùn)行時(shí)直接獲取泛型具體類(lèi)型信息悼瘾,但是 Kotlin 中通過(guò)內(nèi)聯(lián)函數(shù)卻解決了這個(gè)問(wèn)題囊榜,只需為泛型參數(shù)增加
reified
修飾符即可:// 此時(shí)可直接獲取泛型 T 的具體類(lèi)型信息 inline fun <reified T> membersOf() = T::class.members fun main(s: Array<String>) { println(membersOf<StringBuilder>().joinToString("\n")) }
原理其實(shí)就是內(nèi)聯(lián)函數(shù)在編譯時(shí)會(huì)直接將泛型替換為具體類(lèi)型,因此可以在運(yùn)行期間獲取泛型具體信息亥宿。
-
中綴表達(dá)式/中綴函數(shù):可使用過(guò)
infix
關(guān)鍵字定義中綴表達(dá)式/中綴函數(shù)卸勺,中綴表達(dá)式在使用上更加接近自然語(yǔ)言。中綴表達(dá)式必須滿(mǎn)足以下要求才能定義:
- 僅支持成員函數(shù)或擴(kuò)展函數(shù)
- 只支持單參數(shù)函數(shù)
- 不支持默認(rèn)參數(shù)和可變參數(shù)
舉個(gè)例子:定義一個(gè)中綴表達(dá)式
vs
烫扼,用于比較兩個(gè)字符串長(zhǎng)度大小曙求,且返回長(zhǎng)度較長(zhǎng)的字符串:infix fun String.vs(other: String): String { return if (this.length > other.length) this else other } // 調(diào)用 "Hello" vs "World" // => World
-
尾遞歸函數(shù)(Tail recursive function):Kotlin 支持尾遞歸函數(shù)咧虎,其使用
tailrec
修飾符表示自晰。尾遞歸函數(shù)在編譯期間會(huì)被編譯為循環(huán)代碼衷敌,有效規(guī)避遞歸函數(shù)堆棧溢出風(fēng)險(xiǎn)棺蛛。舉個(gè)例子:使用尾遞歸函數(shù)編寫(xiě)一個(gè)階乘運(yùn)算:
tailrec fun factorial(n: Int, total: Int = 1): Long { if (n == 1) { return total.toLong() } return factorial(n - 1, n * total) }
上述代碼會(huì)最終被編譯為類(lèi)似如下的循環(huán)代碼(以 Java 為例):
public static final long factorial(int n, int total) { while(n != 1) { int value = n - 1; total = n * total; n = value; } return (long)total; }
注:Kotlin 中尾遞歸函數(shù)的要求是:尾遞歸函數(shù)的最后一個(gè)表達(dá)式必須是調(diào)用函數(shù)自身摄闸,且不能夾雜其他操作僚焦,比如不能用在
try/catch/finally
塊中茶凳,甚至就連多余連接等操作都不能有(很容易導(dǎo)致最后一個(gè)操作不是調(diào)用自身)择示,否則會(huì)導(dǎo)致尾遞歸無(wú)法編譯為循環(huán)操作豆赏,比如如果將上述階乘計(jì)算改為下述代碼挣菲,就會(huì)導(dǎo)致尾遞歸優(yōu)化失敗:tailrec fun factorial(n: Int): Long { if (n == 1) { return 1L } return n * factorial(n - 1) }
雖然看似上述代碼的最后一個(gè)操作是調(diào)用了函數(shù)自身掷邦,但其實(shí)最后一個(gè)操作是乘法操作白胀,即
n * factorial_result
,這樣就就會(huì)導(dǎo)致尾遞歸失效抚岗,不會(huì)被轉(zhuǎn)化為循環(huán)代碼或杠。
面向?qū)ο?/h2>
Kotlin 支持面向?qū)ο缶幊谭妒剑饕婕暗膬?nèi)容有如下:
類(lèi)
在 Kotlin 中宣蔚,類(lèi)使用class
關(guān)鍵字進(jìn)行修飾向抢,類(lèi)的定義格式如下所示:
[可見(jiàn)性修飾符] class <類(lèi)名> {...}
比如,定義一個(gè)類(lèi)Human
胚委,表示人類(lèi):
class Human {
}
當(dāng)類(lèi)未攜帶任何屬性或方法時(shí)挟鸠,可省略類(lèi)體大括號(hào):
class Human
方法和屬性
類(lèi)中可以定義一些屬性和方法,又由于類(lèi)是對(duì)對(duì)象的抽象亩冬,因此類(lèi)中的屬性和方法依據(jù)歸屬不同又可再細(xì)分為如下:
- 成員屬性:表示對(duì)象的屬性
- 類(lèi)屬性:表示類(lèi)的屬性艘希,在 Java 中也被稱(chēng)為 靜態(tài)屬性
- 成員方法:表示對(duì)象的方法
- 類(lèi)方法:表示類(lèi)的方法,在 Java 中也被稱(chēng)為 靜態(tài)方法
注:由于 Kotlin 支持全局函數(shù)和全局變量(全局函數(shù)和全局屬性會(huì)被編譯為 Java 類(lèi)的靜態(tài)成員),因此 Kotlin 取消了類(lèi)屬性和類(lèi)方法覆享,也即 Kotlin 不支持靜態(tài)成員和靜態(tài)方法佳遂。如果一定要為類(lèi)實(shí)現(xiàn)類(lèi)似 Java 的靜態(tài)調(diào)用(即類(lèi)名.xx
),可以使用 伴生對(duì)象撒顿,具體內(nèi)容請(qǐng)參考后文:面向?qū)ο?- 類(lèi) - 單例類(lèi) - 伴生對(duì)象
注:面向?qū)ο蠓妒街谐笞铮袔讉€(gè)概念比較相近,可能會(huì)讓人有所混淆核蘸,在此進(jìn)行區(qū)分一下:
- 函數(shù)(Function):指一段命名的代碼塊
- 方法(Method):定義在類(lèi)中的函數(shù)即稱(chēng)為方法
-
字段(Field):表示對(duì)象或類(lèi)的成員變量巍糯,主要用于承載數(shù)據(jù),因此通常都是私有的客扎,即
private
-
屬性(Property):可以理解為擁有
getter
和setter
方法的字段
類(lèi)中方法的定義與普通函數(shù)定義格式一致祟峦,類(lèi)中屬性的定義完整格式如下:
// 成員變量
var <變量名>[: <類(lèi)型>] [= <初始化>]
[<getter>]
[<setter>]
// 成員常量
val <變量名>[: <類(lèi)型>] [= <初始化>]
[<getter>]
舉個(gè)例子:為類(lèi)Human
增加一些成員屬性:name
、age
徙鱼、gender
宅楞,以及相應(yīng)的getter
成員方法:
open class Human {
// 完整寫(xiě)法
public var name: String = "unborn"
get() = field // getter
set(value) { // setter
field = value
}
// 省略寫(xiě)法
public var age: Int = 0
// Java 常用的 getter 方式(不推薦,因?yàn)楸旧硪丫邆?getter 方法)
private var gender: String = "unknown"
fun sex() = this.gender
override fun toString(): String {
return "${this.name} - ${this.sex()} - ${this.age}"
}
}
注:setter
和getter
統(tǒng)稱(chēng)為屬性訪(fǎng)問(wèn)器袱吆,其中厌衙,setter
為寫(xiě)訪(fǎng)問(wèn)器,getter
為讀訪(fǎng)問(wèn)器绞绒,訪(fǎng)問(wèn)器中通過(guò)幕后字段field
讀取或設(shè)置字段變量婶希。
最后,Kotlin 還支持屬性委托機(jī)制蓬衡,詳情參考后文:面向?qū)ο?- 類(lèi) - 委托 - 屬性委托
構(gòu)造函數(shù)
構(gòu)造函數(shù)是類(lèi)中一個(gè)特殊的成員方法喻杈,創(chuàng)建對(duì)象時(shí),會(huì)自動(dòng)調(diào)用對(duì)象的相應(yīng)構(gòu)造函數(shù)狰晚,進(jìn)行對(duì)象初始化操作筒饰。
Kotlin 構(gòu)造函數(shù)使用constructor
關(guān)鍵字修飾,且支持如下兩種類(lèi)型構(gòu)造函數(shù):
-
主構(gòu)造函數(shù):主構(gòu)造函數(shù)直接定義在類(lèi)名后面壁晒,且不具備函數(shù)體瓷们,其具體格式如下所示:
class <類(lèi)名> [[可見(jiàn)性修飾符] [注解] constructor](參數(shù)1: 類(lèi)型, 參數(shù)2: 類(lèi)型...) {...}
注:當(dāng)主構(gòu)造函數(shù)未顯示指定可見(jiàn)性修飾符與注解時(shí),可省略
constructor
關(guān)鍵字:class <類(lèi)名> (參數(shù)1: 類(lèi)型, 參數(shù)2: 類(lèi)型...) {...}
對(duì)主構(gòu)造函數(shù)的初始化是通過(guò)初始化代碼塊
init{..}
進(jìn)行的秒咐。
注:類(lèi)中可以有 0 個(gè)或多個(gè)init {..}
代碼塊谬晕,其執(zhí)行順序與聲明順序一致(包含成員變量)舉個(gè)例子:為類(lèi)
Human
增加主構(gòu)造函數(shù),并接收三個(gè)參數(shù)name
携取、age
攒钳、gender
,并進(jìn)行初始化:// 主構(gòu)造函數(shù) class Human(name: String, age: Int, gender: String) { private var name: String = "unborn" private var age: Int = 0 private var gender: String = "unknown" // 初始化代碼塊 init { this.name = name this.age = age this.gender = gender } // ... }
如果主構(gòu)造函數(shù)的參數(shù)直接賦值給對(duì)象成員變量歹茶,那么可以直接在主構(gòu)造函數(shù)中使用
val
或var
修飾夕玩,被val
或var
修飾的變量會(huì)自動(dòng)成為類(lèi)的成員屬性,無(wú)需在類(lèi)內(nèi)顯示聲明和進(jìn)行初始化:class Human(private val name: String, private val age: Int, private val gender: String) { // ... }
最后惊豺,任何一個(gè)類(lèi)燎孟,最多只能有一個(gè)主構(gòu)造函數(shù),且當(dāng)未顯示聲明主構(gòu)造函數(shù)時(shí)尸昧,Kotlin 會(huì)自動(dòng)為每個(gè)類(lèi)生成一個(gè)無(wú)參主構(gòu)造函數(shù)(除非該類(lèi)還聲明了次構(gòu)造函數(shù)揩页,則此時(shí)不生成默認(rèn)主構(gòu)造函數(shù),即主構(gòu)造函數(shù)個(gè)數(shù)為 0)
-
次構(gòu)造函數(shù):次構(gòu)造函數(shù)在類(lèi)內(nèi)聲明烹俗,其格式如下所示:
class <類(lèi)名> { constructor(參數(shù)1: 類(lèi)型, 參數(shù)2: 類(lèi)型...) {...} }
Kotlin 中規(guī)定:一個(gè)類(lèi)可以存在一個(gè)或多個(gè)次構(gòu)造函數(shù)爆侣,但是當(dāng)主構(gòu)造函數(shù)存在時(shí),每個(gè)次構(gòu)造函數(shù)必須直接或間接調(diào)用主構(gòu)造函數(shù)幢妄。
舉個(gè)例子:為類(lèi)
Human
添加多個(gè)次構(gòu)造函數(shù):class Human(private val name: String, private val age: Int, private val gender: String) { // this(..) => 主構(gòu)造函數(shù) constructor(name: String, age: Int) : this(name, age, "unknown") { } // this(..) => 次構(gòu)造函數(shù) => 主構(gòu)造函數(shù)(間接調(diào)用) constructor(name: String) : this(name, 0) { } // ... }
其實(shí)兔仰,次構(gòu)造函數(shù)很少會(huì)用到,因?yàn)?Kotlin 支持函數(shù)默認(rèn)參數(shù)蕉鸳,即上述代碼可簡(jiǎn)化為如下:
class Human(private val name: String, private val age: Int = 0, private val gender: String = "unknown") { // ... }
對(duì)象創(chuàng)建
創(chuàng)建一個(gè)類(lèi)的對(duì)象只需調(diào)用其相應(yīng)的構(gòu)造函數(shù)即可:
// 無(wú)需使用 new 關(guān)鍵字
val human = Human("anonymous")
繼承
類(lèi)的繼承其實(shí)就是基于已有類(lèi)(基類(lèi))派生出新類(lèi)(子類(lèi))乎赴,子類(lèi)自動(dòng)擁有基類(lèi)所有可繼承的屬性和方法。
Kotlin 中的繼承語(yǔ)法如下所示:
// 使用冒號(hào)表示繼承關(guān)系
class <子類(lèi)> : <基類(lèi)>(參數(shù)1, 參數(shù)2...) {...}
注:Kotlin 由于主構(gòu)造函數(shù)沒(méi)有函數(shù)體潮尝,因此繼承的時(shí)候榕吼,基類(lèi)的表現(xiàn)形式是以構(gòu)造函數(shù)形式,用來(lái)表示子類(lèi)主構(gòu)造函數(shù)默認(rèn)調(diào)用父類(lèi)的哪個(gè)構(gòu)造函數(shù)勉失。
當(dāng)基類(lèi)不存在主構(gòu)造函數(shù)時(shí)(即定義了次構(gòu)造函數(shù)羹蚣,但未定義主構(gòu)造函數(shù)時(shí)),且子類(lèi)也不存在主構(gòu)造函數(shù)乱凿,則可以使用類(lèi)名即可:
class <子類(lèi)> : <基類(lèi)> {...}
此時(shí)子類(lèi)的次構(gòu)造函數(shù)必須直接或間接使用super
關(guān)鍵字調(diào)用基類(lèi)相關(guān)構(gòu)造函數(shù)進(jìn)行初始化:
class MyView : View {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}
在 Kotlin 中顽素,所有類(lèi)、屬性和方法默認(rèn)采用final
修飾告匠,因此不能被繼承與覆寫(xiě)戈抄,需要手動(dòng)為支持繼承與覆寫(xiě)的類(lèi)或方法/屬性增加open
修飾符。
舉個(gè)例子:定義一個(gè)新類(lèi)Female
后专,并繼承于Human
划鸽,并覆寫(xiě)方法sex()
:
// 為類(lèi) Human 添加 open 修飾符,使能繼承
open class Human(private val name: String,
private val age: Int = 0,
private val gender: String = "unknown") {
// 為方法增加 open 修飾符戚哎,使能覆寫(xiě)
open fun sex() = this.gender
// ...
}
// 定義新類(lèi)繼承 Human
class Female(name: String, age: Int): Human(name, age, "female") {
// 覆寫(xiě)需要使用 override 關(guān)鍵字
override fun sex(): String {
return "female"
}
}
注:對(duì)屬性或方法的覆寫(xiě)使用關(guān)鍵字override
修飾裸诽。
特殊類(lèi)
Kotlin 內(nèi)置了其余一些特殊功能的類(lèi),大致有如下:
-
內(nèi)部類(lèi):內(nèi)部類(lèi)默認(rèn)持有外部類(lèi)引用型凳,故內(nèi)部類(lèi)可直接訪(fǎng)問(wèn)外部類(lèi)成員丈冬。
內(nèi)部類(lèi)使用關(guān)鍵字
inner
進(jìn)行修飾:class Outter { private val bar = 1 inner class Inner { // 內(nèi)部類(lèi)默認(rèn)只有外部類(lèi)引用,故可直接引用外部類(lèi)成員 fun foo() = bar } } // 調(diào)用 val obj = Outter().Inner().foo() // ==> 1
注:內(nèi)部外訪(fǎng)問(wèn)外部類(lèi)對(duì)象使用
this@Outter
進(jìn)行表示甘畅。 -
嵌套類(lèi):也即 Java 中的靜態(tài)內(nèi)部類(lèi)埂蕊,實(shí)際就只是將類(lèi)定義在一個(gè)類(lèi)里面而已:
class Outer { class Nested { fun foo() = println("nested class") } } // 調(diào)用 val obj: Outer.Nested = Outer.Nested() obj.foo() // => nested class
注:嵌套類(lèi)無(wú)法直接訪(fǎng)問(wèn)外部類(lèi)成員往弓,但是由于處于同一文件中,因此可通過(guò)實(shí)例化外部類(lèi)對(duì)象從而訪(fǎng)問(wèn)外部類(lèi)對(duì)象所有成員(包括私有成員也可直接訪(fǎng)問(wèn))蓄氧。
-
抽象類(lèi):擁有抽象屬性或抽象方法的類(lèi)為抽象類(lèi)函似,抽象類(lèi)使用
abstract
關(guān)鍵字修飾,抽象類(lèi)只能被繼承喉童,無(wú)法實(shí)例化:abstract class AbstractClass { // 抽象屬性 protected abstract var id: Int // 抽象方法 abstract fun absMethod() }
注:如果一個(gè)類(lèi)包含抽象屬性或抽象方法撇寞,那么該類(lèi)必須聲明為抽象類(lèi)(即必須使用
abstract
修飾),但是如果一個(gè)類(lèi)是抽象類(lèi)堂氯,它可以不包含任何抽象屬性或抽象方法蔑担。 -
泛型類(lèi):Kotlin 支持定義泛型類(lèi):
class Box<T>(t: T) { var value = t } // 調(diào)用 val box: Box<Int> = Box<Int>(1)
-
數(shù)據(jù)類(lèi):數(shù)據(jù)類(lèi)是 Kotlin 中一個(gè)十分受歡迎的特性,它可以自動(dòng)生成
equals()
咽白、hashCode()
啤握、toString()
、componentN()
和copy()
等方法晶框,使用數(shù)據(jù)類(lèi)我們就可以不用手動(dòng)寫(xiě)這些模板方法:data class UserBean(val name: String, val age: Int)
注:只有聲明在主構(gòu)造函數(shù)中的屬性才會(huì)被加入到
equals
恨统、hashCode
等自動(dòng)生成的方法中進(jìn)行計(jì)算,聲明在類(lèi)體中的屬性不納入自動(dòng)生成范疇三妈。 -
單例類(lèi):Kotlin 提供了一個(gè)特別的關(guān)鍵字
object
畜埋,其會(huì)創(chuàng)建一個(gè)類(lèi)并同時(shí)實(shí)例化一個(gè)對(duì)象,使用該關(guān)鍵字可創(chuàng)建如下幾種類(lèi)型對(duì)象實(shí)例:-
對(duì)象聲明:對(duì)象聲明是一個(gè)命名單例畴蒲,即創(chuàng)建一個(gè)全局單例對(duì)象:
對(duì)象聲明可以包含屬性悠鞍、方法、初始化語(yǔ)句塊
init {..}
...但不支持構(gòu)造函數(shù)聲明模燥,因此聲明時(shí)即創(chuàng)建了全局單例對(duì)象咖祭,無(wú)需通過(guò)構(gòu)造函數(shù)創(chuàng)建,同時(shí)這也能保證其全局單例特性(實(shí)際底層實(shí)現(xiàn)會(huì)生成一個(gè)構(gòu)造函數(shù)私有的類(lèi))蔫骂。object SingleInstance { fun doSomething(){ println("global single instance") } } fun main() = SingleInstance.doSomething()
注:對(duì)象聲明創(chuàng)建的是一個(gè)立即加載全局單例么翰,其等價(jià) Java 代碼如下:
public final class SingleInstance { @NotNull public static final SingleInstance INSTANCE; private SingleInstance() { } static { INSTANCE = new SingleInstance(); } }
-
對(duì)象表達(dá)式:對(duì)象表達(dá)式會(huì)創(chuàng)建一個(gè)匿名對(duì)象,它相當(dāng)于 Java 中的匿名內(nèi)部類(lèi):
Thread(object : Runnable { override fun run() {...} }).start()
注:如果對(duì)象是函數(shù)式 Java 接口(即只具備一個(gè)抽象方法的 Java 接口辽旋,單抽象方法接口)實(shí)例浩嫌,那么在 Kotlin 中可將其視為一個(gè)函數(shù)類(lèi)型,即上述代碼可更改為如下:
Thread(Runnable { override fun run() {...} }).start()
其實(shí)就是將
Runnable
看成一個(gè)函數(shù)补胚,其參數(shù)為一個(gè) Lambda 表達(dá)式溶其。另外骚腥,如果 Java 的函數(shù)式接口,其抽象方法只有 0 個(gè)或 1 個(gè)參數(shù)時(shí)瓶逃,此時(shí)可省略接口名:
Thread{ ... }.start()
-
伴生對(duì)象:可以為類(lèi)定義一個(gè)伴生對(duì)象束铭,實(shí)現(xiàn)靜態(tài)調(diào)用廓块。
伴生對(duì)象處理可以實(shí)現(xiàn)靜態(tài)調(diào)用外,另一個(gè)好處就是可以訪(fǎng)問(wèn)外部類(lèi)的私有成員:
class Outter { private val str = "private Outter variable" companion object Companion{ // 定義外部類(lèi)實(shí)例作為成員變量 private val outter = Outter() fun say() { // 直接訪(fǎng)問(wèn)外部類(lèi)私有成員 println(outter.str) } } } // 類(lèi)似于 Java 一樣的靜態(tài)調(diào)用 Outter.say()
Kotlin 中規(guī)定契沫,每個(gè)類(lèi)中最多只能有一個(gè)伴生對(duì)象剿骨,因此伴生對(duì)象可省略名稱(chēng),即
companion object Companion {...}
可簡(jiǎn)寫(xiě)為companion object {...}
注:伴生對(duì)象在編譯時(shí)會(huì)被編譯為 Java 中的靜態(tài)內(nèi)部類(lèi)埠褪。
-
-
枚舉類(lèi):枚舉類(lèi)的主要特性是其內(nèi)部會(huì)定義相關(guān)類(lèi)實(shí)例對(duì)象,以限制該類(lèi)型實(shí)例對(duì)象挤庇。
Kotlin 中枚舉類(lèi)使用關(guān)鍵字
enum
修飾:enum class Direction { NORTH, SOUTH, WEST, EAST }
枚舉類(lèi)內(nèi)部可以聲明的內(nèi)容與普通類(lèi)一致钞速,因此可以定義構(gòu)造函數(shù),可以定義抽象方法...:
enum class Color(val rgb: Int) { RED(0xFF0000) { override fun paint(): String { return "red: ${this.rgb}" } }, GREEN(0x00FF00) { override fun paint(): String { return "green: ${this.rgb}" } }, BLUE(0x0000FF) { override fun paint(): String { return "blue: ${this.rgb}" } }; // 注:此處需要使用分號(hào) abstract fun paint(): String } fun main() = println(Color.RED.paint()) // => red: 0xFF0000
-
密封類(lèi):Kotlin 中使用密封類(lèi)來(lái)定義受限的類(lèi)繼承結(jié)構(gòu)嫡秕。
密封類(lèi)使用
sealed
關(guān)鍵字修飾渴语,被sealed
修飾的密封類(lèi)默認(rèn)為abstract
,且其構(gòu)造函數(shù)為private
昆咽。枚舉類(lèi)是將實(shí)例定義在同一個(gè)類(lèi)中驾凶,而密封類(lèi)則要求將子類(lèi)定義在同一個(gè)基類(lèi)中,或者定義在基類(lèi)所在的文件中(因?yàn)?code>sealed class構(gòu)造函數(shù)是私有的)掷酗,以此控制子類(lèi)繼承結(jié)構(gòu)调违。
換種說(shuō)法,枚舉類(lèi)可認(rèn)為是限定了其實(shí)例數(shù)量泻轰,而密封類(lèi)可認(rèn)為是限定了其子類(lèi)數(shù)量技肩,因?yàn)樗鼈兌紝?xiě)死在了與基類(lèi)同一處位置。
如果把有限狀態(tài)看成類(lèi)集合浮声,那么密封類(lèi)就是一種非常適合的定義結(jié)構(gòu)虚婿,將狀態(tài)定義為密封基類(lèi),然后子類(lèi)覆寫(xiě)基類(lèi)泳挥,更改狀態(tài)然痊,由于狀態(tài)有限,因此屉符,子類(lèi)個(gè)數(shù)是有限的剧浸,并且密封類(lèi)支持
when
表達(dá)式,且數(shù)量有限制矗钟,因此無(wú)須使用多余的else
語(yǔ)句辛蚊,使得狀態(tài)選擇非常容易:sealed class State data class success(val status: Int) : State() data class failure(val status: Int, val extra: String) : State() fun checkState(state: State) = when (state) { is success -> println(state.status) is failure -> println("${state.status} ${state.extra}") } // 調(diào)用 fun main() = checkState(failure(404, "not found")) // => 404 not found
-
內(nèi)聯(lián)類(lèi):Kotlin 中使用
inline
關(guān)鍵字修飾內(nèi)聯(lián)類(lèi)。內(nèi)聯(lián)類(lèi)的主要作用是對(duì)基本數(shù)據(jù)類(lèi)型的包裝真仲,因此基本數(shù)據(jù)類(lèi)型的表達(dá)范圍比較大袋马,有時(shí)無(wú)法清楚表達(dá)變量代表的含義,比如秸应,假設(shè)有一個(gè)變量:
val time: Int
虑凛,該變量的類(lèi)型為Int
碑宴,但是我們無(wú)法直接知道該Int
是采用秒、分鐘還是小時(shí)作為單位桑谍,此時(shí)如果能將該Int
包裝為具體的類(lèi)型延柠,比如Second
、Minute
或者Hour
的話(huà)锣披,就能明確具體類(lèi)型贞间,方便調(diào)用。但是重新定義一個(gè)新的類(lèi)型雹仿,運(yùn)行時(shí)會(huì)有對(duì)象創(chuàng)建等開(kāi)銷(xiāo)增热,本質(zhì)上我們只需一個(gè)基本數(shù)據(jù)類(lèi)型數(shù)據(jù),而借助內(nèi)聯(lián)類(lèi)胧辽,就可以同時(shí)實(shí)現(xiàn)上述需求峻仇。
內(nèi)聯(lián)類(lèi)在源碼中相當(dāng)于定義了一個(gè)新的類(lèi)型,但在編譯時(shí)邑商,會(huì)被編譯器拆箱為基本數(shù)據(jù)類(lèi)型摄咆,消除運(yùn)行時(shí)對(duì)象創(chuàng)建開(kāi)銷(xiāo)。
舉個(gè)例子:創(chuàng)建一個(gè)內(nèi)聯(lián)類(lèi)
Hour
人断,表示小時(shí):inline class Hour(val time: Int) {} // 調(diào)用 val time = Hour(2) // => 實(shí)際被編譯為:int time = 2
內(nèi)聯(lián)類(lèi)支持部分普通類(lèi)特性吭从,比如屬性和方法定義,但有如下一些限制:
- 內(nèi)聯(lián)類(lèi)必須具備主構(gòu)造函數(shù)恶迈,且主構(gòu)造函數(shù)只能接受一個(gè)只讀(即
val
)基礎(chǔ)類(lèi)型數(shù)據(jù)影锈,編譯器最終拆箱為該基礎(chǔ)類(lèi)型數(shù)據(jù) - 內(nèi)聯(lián)類(lèi)不能有
init
塊 - 內(nèi)聯(lián)類(lèi)內(nèi)部屬性不能有幕后字段,即不能使用
field
蝉绷,因此內(nèi)部屬性是對(duì)基礎(chǔ)類(lèi)型屬性的計(jì)算代理 - 內(nèi)聯(lián)類(lèi)內(nèi)部屬性不能是
lateinit
和委托屬性 - 內(nèi)聯(lián)類(lèi)不支持繼承鸭廷,既不能繼承其他類(lèi),也不能被其他類(lèi)繼承(因?yàn)樽罱K編譯后的類(lèi)型為基礎(chǔ)數(shù)據(jù)類(lèi)型)
- 內(nèi)聯(lián)類(lèi)支持繼承接口
- 內(nèi)聯(lián)類(lèi)必須具備主構(gòu)造函數(shù)恶迈,且主構(gòu)造函數(shù)只能接受一個(gè)只讀(即
-
局部類(lèi):局部類(lèi)是定義在函數(shù)/方法中的類(lèi)(不常用到):
class Outter { // 局部類(lèi)不能訪(fǎng)問(wèn)外部類(lèi)成員 private var outter_var = "outter variable" fun func() { var func_var = "func variable" class LocalClass { fun say() { println("inside local class") // 可修改外部方法局部變量 func_var = "change func variable" // 可訪(fǎng)問(wèn)外部方法局部變量 println(func_var) } } LocalClass().say() } }
局部類(lèi)只在定義它的方法內(nèi)可見(jiàn)熔吗,因此局部類(lèi)不能調(diào)用外部類(lèi)任何成員辆床,但局部類(lèi)可以訪(fǎng)問(wèn)和修改外部方法局部變量。
委托
Kotlin 支持對(duì)類(lèi)的委托和對(duì)屬性的委托桅狠,委托即代理讼载,通過(guò)將類(lèi)或?qū)傩晕薪o其他對(duì)象,讓其他對(duì)象代理類(lèi)或?qū)傩缘男袨椤?/p>
Kotlin 中通過(guò)關(guān)鍵字by
指定委托對(duì)象中跌。
-
類(lèi)委托:類(lèi)委托即委托類(lèi)將自己的行為委托給被委托類(lèi)(即代理類(lèi))咨堤,一個(gè)要求是,委托類(lèi)和被委托類(lèi)必須是同一類(lèi)型漩符,這樣 Kotlin 就能為委托類(lèi)自動(dòng)生成相應(yīng)的方法一喘,這些方法內(nèi)部實(shí)現(xiàn)都由被委托類(lèi)進(jìn)行代理,如果委托類(lèi)內(nèi)部實(shí)現(xiàn)了相應(yīng)方法,則不走代理凸克,而是使用自己的方法:
// 共同類(lèi)型 interface IWorker { fun doWork() fun relax() } // 秘書(shū) class Secretary: IWorker{ // 工作當(dāng)然是交給秘書(shū)去做 override fun doWork() { println("Secretary do work") } override fun relax() { println("Secretary relax") } } // 老板 委托給 秘書(shū) class Boss : IWorker by Secretary() { // 休息當(dāng)然是老板自己休息 override fun relax() { println("Boss relax") } } fun main() { val boss = Boss() boss.doWork() // => Secretary do work议蟆,代理成功 boss.relax() // => Boss relax,不走代理 }
上述代碼直接寫(xiě)死了代理類(lèi)萎战,比較不靈活(代理類(lèi)和被代理類(lèi)緊耦合)咐容,其實(shí)可以將代理類(lèi)作為委托類(lèi)的一個(gè)屬性,代理給該屬性對(duì)象即可蚂维,這樣外部只需更改傳入的代理對(duì)象戳粒,就可實(shí)現(xiàn)不同代理行為,擴(kuò)展性更好:
//... // 組合優(yōu)先于繼承 class Boss(private val worker: IWorker) : IWorker by worker { override fun relax() { println("Boss relax") } } fun main() { // 外部傳入代理類(lèi)虫啥,解耦 val boss = Boss(Secretary()) boss.doWork() // => Secretary do work蔚约,代理成功 boss.relax() // => Boss relax,不走代理 }
-
屬性委托:可以將屬性委托給代理類(lèi)對(duì)象孝鹊,對(duì)該屬性的訪(fǎng)問(wèn)與賦值會(huì)委托給代理對(duì)象實(shí)現(xiàn)。
屬性委托的執(zhí)行機(jī)制為:屬性的
get()
方法會(huì)委托給代理對(duì)象的getValue()
方法展蒂,屬性的set()
方法會(huì)委托給代理對(duì)象的setValue()
方法又活,因此屬性委托不要求屬性與代理對(duì)象類(lèi)型一致,只需代理對(duì)象實(shí)現(xiàn)相應(yīng)的getValue()
方法和setValue()
方法锰悼,其中柳骄,val
屬性只需代理對(duì)象實(shí)現(xiàn)getValue()
,var
屬性代理對(duì)象需同時(shí)實(shí)現(xiàn)getvalue()
和setValue()
方法箕般。class Boss { // 老板的報(bào)告交給秘書(shū)做 var report: String by Secretary() } class Secretary { // thisRef 為被代理類(lèi)對(duì)象耐薯,即 Boss, // property 為被代理屬性 operator fun getValue(thisRef: Any?, property: KProperty<*>): String { return "被代理類(lèi):$thisRef丝里,被代理屬性:${property.name}" } // s 為要設(shè)置的值 operator fun setValue(thisRef: Any?, property: KProperty<*>, s: String) { println("$thisRef.${property.name} = $s") } } fun main() { val boss = Boss() boss.report = "make a report" // setValue() println(boss.report) // getValue() }
接口
接口的特性是所有方法都默認(rèn)為抽象方法曲初,所有屬性都默認(rèn)為抽象屬性,且不能具備狀態(tài)(即不能定義普通屬性)杯聚【势牛可以將接口當(dāng)做一種特殊的抽象類(lèi),其使用interface
進(jìn)行修飾幌绍。
interface IVoice {
// 默認(rèn) abstract
val who: String
fun description(): String
// 可以有默認(rèn)實(shí)現(xiàn)
fun sound(): String {
return "quiet"
}
}
fun main() {
val sheep: IVoice = object : IVoice {
override val who: String = "Sheep"
override fun description(): String = this.who
override fun sound() = "mie mie"
}
println("${sheep.description()} sound like: ${sheep.sound()}")
}
注:Kotlin 中的接口方法支持默認(rèn)實(shí)現(xiàn)颁褂,當(dāng)未重寫(xiě)時(shí),則使用默認(rèn)實(shí)現(xiàn)傀广。
運(yùn)算符 / 操作符
Kotlin 支持如下幾種類(lèi)型運(yùn)算符操作:
-
算術(shù)運(yùn)算符(Arithmetic Operators):執(zhí)行代數(shù)運(yùn)算颁独。常見(jiàn)算術(shù)運(yùn)算符如下表所示:
Operator Description +
加法運(yùn)算 -
減法運(yùn)算 *
乘法運(yùn)算 /
除法運(yùn)算 %
取余 注:除法運(yùn)算符
/
需要注意下是否是整除(都是整型情況下)注:
*
也可作為展開(kāi)運(yùn)算符使用,常用于展開(kāi)數(shù)組傳遞給可變參數(shù) -
賦值運(yùn)算符(Assignment Operators):執(zhí)行變量賦值操作伪冰。常見(jiàn)賦值運(yùn)算符如下表所示:
Operator Description =
等于(賦值) +=
加等 -=
減等 *=
乘等 /=
除等 %=
余等 -
關(guān)系/比較運(yùn)算符(Relational (Comparison) Operators):執(zhí)行比較操作誓酒。常見(jiàn)關(guān)系運(yùn)算符如下表所示:
Operator Description <
小于 >
大于 <=
小于或等于 >=
大于或等于 ==
等于 !=
不等于 ===
同一對(duì)象 !==
不是同一對(duì)象
-
邏輯運(yùn)算符(Logical Operators):執(zhí)行邏輯運(yùn)算。常見(jiàn)邏輯運(yùn)算符如下表所示:
Operator Description ||
或運(yùn)算 &&
與運(yùn)算 !
非運(yùn)算 -
位運(yùn)算符(Bitwise Operators):執(zhí)行位運(yùn)算(比特操作)贮聂。常見(jiàn)位運(yùn)算符如下表所示:
Operator Description and
與 or
或 inv
非(取反) xor
異或 shl
有符號(hào)左移 shr
有符號(hào)右移 ushl
無(wú)符號(hào)左移 ushr
無(wú)符號(hào)右移 -
一元運(yùn)算符(Unary Operators):有些編程語(yǔ)言支持一元運(yùn)算符丰捷。常見(jiàn)的一元運(yùn)算符如下表所示:
Operator Description +
正號(hào) -
負(fù)號(hào)(正負(fù)數(shù)轉(zhuǎn)換) ++
自增 --
自減 注:一元運(yùn)算符中的
++
和--
同時(shí)支持前置和后置功能坯墨。 if
表達(dá)式:Kotlin 中的if
表達(dá)式相當(dāng)于三目運(yùn)算符,其格式如下:
if <expression> <TruthValue> else <FalseValue>
注:表達(dá)式(Expression) 和 語(yǔ)句(Statement) 的區(qū)別是:表達(dá)式有返回值病往,而語(yǔ)句不一定有捣染。
即當(dāng)expression
為true
時(shí),返回TruthValue
停巷,否則返回FalseValue
耍攘。
舉個(gè)例子:if 3 > 2 "true" else "false"
,條件為3 > 2
畔勤,返回字符串"true"
流程控制
Kotlin 中的流程控制語(yǔ)句大致有如下幾種:
-
選擇結(jié)構(gòu)語(yǔ)句:主要有如下兩種選擇/條件判斷語(yǔ)句:
-
if
語(yǔ)句:條件判斷語(yǔ)句蕾各,其格式如下所示:if (<expression1>) { ... } else if (<expression2>) { // else if 可多個(gè),也可忽略 ... } else { // else 也可忽略 ... }
注:
if
也可用作表達(dá)式庆揪,其會(huì)返回一個(gè)值 -
when
語(yǔ)句:when
語(yǔ)句類(lèi)似于 Java 中的switch
語(yǔ)句:// 格式一:帶參數(shù) when (x) { 1 -> print("x == 1") 2 -> print("x == 2") else -> { // Note the block print("x is neither 1 nor 2") } } // 格式二:只帶 Lambda 表達(dá)式 when { x.isOdd() -> print("x is odd") y.isEven() -> print("y is even") else -> print("x+y is odd.") }
注:
when
語(yǔ)句也可作為表達(dá)式式曲,其會(huì)返回一個(gè)值:val result: String = when(x) { 1 -> "one" 2 -> "two" else -> "NaN" }
-
-
循環(huán)結(jié)構(gòu)語(yǔ)句:Kotlin 中主要有如下幾種循環(huán)語(yǔ)句:
-
while
循環(huán):其格式如下所示:// 當(dāng) expression 為 true 時(shí),執(zhí)行循環(huán) while(<expression>) { ... }
-
do while
循環(huán):其格式如下所示:do { ... } while(<expression>)
注:Kotlin 中的
do while
語(yǔ)句與其他語(yǔ)言的一個(gè)不同之處在于缸榛,位于do
語(yǔ)句體中的變量吝羞,while
語(yǔ)句中是可見(jiàn)的掘托,而其他大多數(shù)語(yǔ)言時(shí)不可見(jiàn)的:fun main() { do { var x = 10 } while (--x != 0) // x is visible here! }
-
for
循環(huán):for
語(yǔ)句可遍歷任何攜帶迭代器的對(duì)象:// 遍歷集合 for (item in collection) print(item) // 遍歷區(qū)間 for (i in 1..3) { println(i) } ...
注:對(duì)數(shù)組和集合的遍歷還可以使用
forEach(..)
/forEachIndexed(..)
函數(shù)弟疆。
-
跳轉(zhuǎn)語(yǔ)句:Kotlin 中的跳轉(zhuǎn)語(yǔ)句主要有:
continue
、break
和return
前计,與其他語(yǔ)言功能一致均澳,不再贅述恨溜。
代碼組織
Kotlin 中的代碼組織(及導(dǎo)包)與 Java 類(lèi)似,每個(gè) Kotlin 文件都屬于某個(gè)包找前,每個(gè)文件都以package
關(guān)鍵字開(kāi)頭糟袁,用以指定包名,文件中定義的所有聲明(類(lèi)躺盛、函數(shù)及屬性)都隸屬于當(dāng)前文件所在的包系吭。同個(gè)包中的聲明可直接使用,不同包中的聲明需先導(dǎo)入import
后才能使用:
package com.whyn.demos
import kotlin.properties.Delegates
import kotlin.reflect.KProperty
注:如果存在相同聲明颗品,可使用as
關(guān)鍵字設(shè)置一個(gè)別名肯尺,以解決命名沖突:
import com.whyn1.bean.User
import com.whyn2.bean.User as User2 // User2 為 com.whyn2.bean.User 的別名
注釋
Kotlin 支持如下三種類(lèi)型注釋?zhuān)?/p>
-
單行注釋:只注釋當(dāng)前行:
fun main() { // 單行注釋 }
-
多行注釋:可注釋多行內(nèi)容:
fun main() { /* 多行注釋 val str = "Hello World" println(str) */ }
-
文檔注釋:對(duì)聲明進(jìn)行注釋?zhuān)?/p>
/** * Standard property delegates. */ public object Delegates {...}
區(qū)間操作
詳情參考思維導(dǎo)圖
解構(gòu)聲明
Kotlin 可以直接把一個(gè)對(duì)象賦值給多個(gè)變量,這種操作稱(chēng)為 解構(gòu)聲明(Destructuring Declaration)躯枢。
比如:將一個(gè)數(shù)據(jù)類(lèi)User
對(duì)象進(jìn)行解構(gòu):
fun main() {
data class User(var name: String, val age: Int)
val (name, age) = User("Lisi", 30)
println("$name is $age years old") // => Lisi is 30 years old
}
解構(gòu)聲明的原理是類(lèi)中重載了comonentN
方法则吟,進(jìn)行解構(gòu)賦值時(shí),會(huì)依次調(diào)用獲取類(lèi)對(duì)象component1()
锄蹂、component2
...氓仲,賦值給相應(yīng)位置變量。
注:如果無(wú)需獲取某個(gè)解構(gòu)變量,則使用_
占位即可(被_
占位的變量不會(huì)調(diào)用對(duì)應(yīng)的componentN
方法):
// 忽略 age 變量
val (name, _) = User("Lisi", 30)
安全操作運(yùn)算符
對(duì)于可空類(lèi)型敬扛,Kotlin 提供了幾種非常有用的輔助調(diào)用操作晰洒,可以讓我們安全地進(jìn)行非空類(lèi)型操作:
-
安全調(diào)用運(yùn)算符:使用
?.
可以將null
檢查和方法調(diào)用合并為一個(gè)操作,當(dāng)變量為null
時(shí)啥箭,直接返回null
谍珊,而當(dāng)變量不為null
時(shí),則調(diào)用相應(yīng)方法/屬性:fun isEmpty(str: String?): Boolean { return str?.length == 0 }
上述代碼等價(jià)如下:
fun isEmpty(str: String?): Boolean { var ret = false if (str != null) { ret = str.length == 0 } return ret }
-
Elvis
運(yùn)算符:?:
接收兩個(gè)運(yùn)算數(shù)急侥,如果第一個(gè)運(yùn)算數(shù)不為null
砌滞,則返回第一個(gè)運(yùn)算數(shù),否則返回第二個(gè)運(yùn)算數(shù):fun isEmpty(str: String?): Boolean { val ret = str ?: "" return ret.isEmpty() }
上述代碼等價(jià)如下:
fun isEmpty(str: String?): Boolean { var ret: String if (str != null) { ret = str } else { ret = "" } return ret.isEmpty() }
-
非空斷言:
!!
可以將任何值轉(zhuǎn)換為非空類(lèi)型:fun isEmpty(str: String?): Boolean { return str!!.isEmpty() }
上述代碼強(qiáng)制將
str
轉(zhuǎn)換為非空類(lèi)型坏怪,當(dāng)str
為null
時(shí)贝润,會(huì)拋出KotlinNullPointerException
,因此只有在確定變量絕對(duì)不為null
時(shí)铝宵,才可嘗試使用非空斷言打掘。 -
安全轉(zhuǎn)換運(yùn)算符:
as?
可以將可空變量轉(zhuǎn)換為指定類(lèi)型,如果無(wú)法進(jìn)行轉(zhuǎn)換鹏秋,則返回null
:fun isEmpty(obj: Any?): Boolean { var ret: String? = obj as? String ret = ret ?: "" return ret!!.isEmpty() }
操作符重載(Operator overloading)
Kotlin 支持操作符重載尊蚁,只需覆寫(xiě)相應(yīng)操作符方法即可。比如:
data class Point(val x: Int, val y: Int) {
operator fun plus(other: Point): Point {
return Point(this.x + other.x, this.y + other.y)
}
}
上述代碼為Point
類(lèi)重載了運(yùn)算符+
拼岳,其對(duì)應(yīng)的操作符方法為plus
枝誊,這樣况芒,Point
對(duì)象就可以使用操作符+
生成一個(gè)新對(duì)象:
fun main() {
val p1 = Point(1, 2)
val p2 = Point(10, 20)
println(p1 + p2) // => Point(x=11, y=22)
}
常見(jiàn)的操作符重載方法如下所示:
-
一元操作符:如下表所示:
表達(dá)式 操作符方法 +a
a.unaryPlus()
-a
a.unaryMinus()
!a
a.not()
++a, a++
a.inc()
--a, a--
a.dec()
-
二元操作符:如下表所示:
表達(dá)式 操作符方法 a + b
a.plus(b)
a - b
a.minus(b)
a * b
a.times(b)
a / b
a.div(b)
a % b
a.rem(b)
a..b
a.rangeTo(b)
-
in
操作符:如下表所示:表達(dá)式 操作符方法 a in b
b.contains(a)
a !in b
!b.contains(a)
-
索引訪(fǎng)問(wèn)操作符:如下表所示:
表達(dá)式 操作符方法 a[i]
a.get(i)
a[i, j]
a.get(i, j)
a[i_1, ..., i_n]
a.get(i_1, ..., i_n)
a[i] = b
a.set(i, b)
a[i, j] = b
a.set(i, j, b)
a[i_1, ..., i_n] = b
a.set(i_1, ..., i_n, b)
-
函數(shù)調(diào)用操作符:如下表所示:
表達(dá)式 操作符方法 a()
a.invoke()
a(i)
a.invoke(i)
a(i, j)
a.invoke(i, j)
a(i_1, ..., i_n)
a.invoke(i_1, ..., i_n)
-
賦值運(yùn)算符:如下表所示:
表達(dá)式 操作符方法 a += b
a.plusAssign(b)
a -= b
a.minusAssign(b)
a *= b
a.timesAssign(b)
a /= b
a.divAssign(b)
a %= b
a.remAssign(b)
-
關(guān)系/比較操作符:如下表所示:
表達(dá)式 操作符方法 a > b
a.compareTo(b) > 0
a < b
a.compareTo(b) < 0
a >= b
a.compareTo(b) >= 0
a <= b
a.compareTo(b) <= 0
a == b
a?.equals(b) ?: (b === null)
a != b
!(a?.equals(b) ?: (b === null))
更多詳細(xì)內(nèi)容惜纸,請(qǐng)參考:Operator overloading
異常機(jī)制
Java 采用 Check Exception 異常機(jī)制,即當(dāng)調(diào)用處可能出現(xiàn)受檢異常時(shí)绝骚,必須顯示對(duì)異常進(jìn)行聲明(throws
)或捕獲(try..catch
)(非受檢異常不強(qiáng)制進(jìn)行聲明或捕獲)耐版。然而,在 Kotlin 中压汪,卻取消了這種異常檢測(cè)機(jī)制粪牲,根據(jù) Kotlin官方 說(shuō)法,主要原因是 Check Exception 在大型項(xiàng)目上止剖,對(duì)代碼質(zhì)量并無(wú)本質(zhì)提升腺阳,且由于繁瑣的格式,會(huì)降低開(kāi)發(fā)效率穿香。
理論上來(lái)說(shuō)亭引,Kotlin 取消了異常檢測(cè)機(jī)制,可能會(huì)造成程序運(yùn)行中由于缺少必要的異常捕獲而導(dǎo)致程序崩潰率上升皮获,然而實(shí)際中統(tǒng)計(jì)得出焙蚓,Kotlin 開(kāi)發(fā)的程序性并未比 Java 開(kāi)發(fā)的程序健壯性差,但代碼卻更加簡(jiǎn)潔。
最后购公,如果代碼調(diào)用處確實(shí)很可能在運(yùn)行中出現(xiàn)異常萌京,那么手動(dòng)進(jìn)行捕獲即可。
更多 Kotlin 異常機(jī)制內(nèi)容宏浩,可參考:淺談Kotlin的Checked Exception機(jī)制
泛型
Kotlin 支持泛型函數(shù)和泛型類(lèi)...詳細(xì)內(nèi)容可參考上文知残,這里主要介紹下泛型機(jī)制中的幾個(gè)特性:
-
協(xié)變:一個(gè)協(xié)變類(lèi)是一個(gè)泛型類(lèi),協(xié)變類(lèi)保持了泛型間的繼承關(guān)系绘闷。協(xié)變采用關(guān)鍵字
out
進(jìn)行表示橡庞。具體來(lái)說(shuō),假設(shè)類(lèi)
A
繼承于類(lèi)B
印蔗,那么扒最,Producer<A>
與Producer<B>
實(shí)際上是沒(méi)有任何關(guān)系的,但是如果將泛型類(lèi)聲明為協(xié)變华嘹,即Producer<out T : B>
吧趣,那么,Producer<A>
就是Producer<B>
的子類(lèi)耙厚,也即强挫,協(xié)變可以保留子類(lèi)型化關(guān)系(保留泛型繼承關(guān)系)。注:Kotin 中的協(xié)變薛躬,即
out T
相當(dāng)于 Java 中的? extends T
俯渤,更多具體內(nèi)容可參考:理解<? extends T>,<? super T> -
逆變:協(xié)變可以用于保持泛型繼承關(guān)系,而逆變卻反轉(zhuǎn)了泛型繼承關(guān)系型宝。逆變采用關(guān)鍵字
in
進(jìn)行表示八匠。具體來(lái)說(shuō),假設(shè)類(lèi)
A
繼承于類(lèi)B
趴酣,那么梨树,對(duì)于逆變Consumer<in T : B>
,則有:Consumer<B>
是Consumer<A>
的子類(lèi)型岖寞,可以看到抡四,逆變將泛型繼承關(guān)系反轉(zhuǎn)了。注:Kotin 中的協(xié)變仗谆,即
in T
相當(dāng)于 Java 中的? super T
指巡,更多具體內(nèi)容可參考:理解<? extends T>,<? super T> -
星號(hào)投影:即采用符號(hào)
*
來(lái)代替類(lèi)型參數(shù)。具體來(lái)說(shuō)隶垮,星號(hào)投影(比如
List<*>
)表示容器存儲(chǔ)了某種特定類(lèi)型元素藻雪,但具體類(lèi)型未知,因此不能對(duì)容器進(jìn)行存儲(chǔ)操作(因?yàn)榫唧w類(lèi)型未知)岁疼,但可以進(jìn)行獲取操作(因?yàn)闊o(wú)論是哪種類(lèi)型阔涉,都可以看做Any?
)缆娃。當(dāng)不關(guān)心容器存儲(chǔ)元素類(lèi)型信息時(shí)(即T
),可直接使用星號(hào)投影表示元素瑰排。注:星號(hào)投影
List<*>
相當(dāng)于 Java 中的List<?>
贯要。
帶接收者的 Lambda
Kotlin 標(biāo)準(zhǔn)庫(kù)提供了一些很好用的擴(kuò)展函數(shù),下面介紹幾個(gè)常用的 作用域函數(shù)(Scoping Function):
-
run
:run
函數(shù)接收一個(gè)函數(shù)參數(shù)椭住,且返回值為函數(shù)參數(shù)返回值(Lambda 最后一行)崇渗,其源碼如下所示:// 擴(kuò)展函數(shù) @kotlin.internal.InlineOnly public inline fun <T, R> T.run(block: T.() -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block() } // 全局函數(shù) @kotlin.internal.InlineOnly public inline fun <R> run(block: () -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block() }
示例:如下所示:
fun main() { val str = "Outter" val ret = str.run { println(this) // => Outter val str = "Inner" println("inside run" + str) // => Innder,局部作用域 str } println(ret) // => Inner println(str) // => Outter京郑,外部作用域 }
run
函數(shù)可以用于創(chuàng)建局部作用域宅广,其特點(diǎn)在于 Lambda 接收者為this
(即調(diào)用者),返回值為 Lambda 最后一行些举。 -
with
:with
相對(duì)來(lái)說(shuō)比較特殊跟狱,它不是一個(gè)擴(kuò)展函數(shù),with
接收一個(gè)對(duì)象和該對(duì)象類(lèi)型的擴(kuò)展函數(shù)户魏,擴(kuò)展函數(shù)參數(shù)的上下文綁定到了該對(duì)象上驶臊,且其返回值為擴(kuò)展函數(shù)返回值,其源碼如下所示:@kotlin.internal.InlineOnly public inline fun <T, R> with(receiver: T, block: T.() -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return receiver.block() // 綁定上下文 }
示例:如下所示:
fun main() { val str: String = with(StringBuilder()) { this.append("Hello ") this.append("World") this.toString() // 返回值 } println(str) // => Hello World }
with
的主要特性為指定了 Lambda 函數(shù)上下文叼丑,且其返回值為 Lambda 函數(shù)的返回值(即 Lambda 最后一行) -
let
:let
函數(shù)是調(diào)用者的擴(kuò)展函數(shù)关翎,其將調(diào)用者以參數(shù)形式傳遞給 Lambda,因此 Lambda 內(nèi)部作用域上下文可以使用it
來(lái)獲取調(diào)用對(duì)象鸠信,其返回值為 Lambda 最后一行:@kotlin.internal.InlineOnly public inline fun <T, R> T.let(block: (T) -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block(this) }
示例:如下所示:
fun main() { val str: String? = "Hello" val ret = str?.let { println(it) // => Hello "World" // 返回值 } println(ret) // => World }
-
also
:also
與let
基本一樣纵寝,除了返回值為調(diào)用對(duì)象,即this
:@kotlin.internal.InlineOnly @SinceKotlin("1.1") public inline fun <T> T.also(block: (T) -> Unit): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } block(this) return this // 返回調(diào)用者 }
示例:如下所示:
fun main() { val str: String? = "Hello" val ret = str?.also { println(it) // => Hello } println(ret === str) // => true }
-
apply
:apply
函數(shù)是調(diào)用對(duì)象類(lèi)型擴(kuò)展函數(shù)星立,其參數(shù)也為調(diào)用對(duì)象類(lèi)型擴(kuò)展函數(shù)爽茴,因此 Lambda 內(nèi)部作用域上下文為調(diào)用對(duì)象this
,且其返回值也為調(diào)用對(duì)象this
:@kotlin.internal.InlineOnly public inline fun <T> T.apply(block: T.() -> Unit): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } block() return this }
示例:如下所示:
fun main() { val str: String? = "Hello" val ret = str?.apply { println(this) // => Hello } println(ret === str) // => true }
綜上贞铣,其實(shí)可以看出闹啦,這些作用域函數(shù)之間的特性與區(qū)別主要在于如下三方面:
- 都具備局部作用域
- 作用域的接收者為
this
或it
- 都具備返回值:返回值為 Lambda 的返回值(Lambda 最后一行)或調(diào)用者自身(
this
)
具體如下表所示:
函數(shù) | Lambda 內(nèi)部接收者 | 函數(shù)返回值 |
---|---|---|
run |
this |
Lambda 最后一行 |
with |
this |
Lambda 最后一行 |
let |
it |
Lambda 最后一行 |
also |
it |
this |
apply |
this |
this |
最后沮明,個(gè)人覺(jué)得辕坝,其實(shí)這些作用域函數(shù)選擇還是主要在于返回值上,因?yàn)闊o(wú)論哪個(gè)函數(shù)荐健,接收者都可以獲取得到(無(wú)非就是通過(guò)this
或it
的區(qū)別而已)酱畅,而如果想函數(shù)調(diào)用完成后,返回調(diào)用者自身江场,那么可以選擇also
或apply
纺酸,如果想返回 Lambda 函數(shù)返回值,那么可選擇run
址否、with
或let
餐蔬。
大多數(shù)情況下碎紊,使用run
(或let
)和apply
即可滿(mǎn)足所有操作。
協(xié)程
具體內(nèi)容請(qǐng)參考:Kotlin - 協(xié)程 簡(jiǎn)介