重學(xué)編程語(yǔ)言 - Kotlin

簡(jiǎn)介

KotlinJetBrains 公司開(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^{31}) 2,147,483,647 (2^{31} - 1)
      Long 64 -9,223,372,036,854,775,808 (-2^{63}) 9,223,372,036,854,775,807 (2^{63} - 1)

      舉個(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è)值:truefalse

    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剑勾、ShortArrayIntArray等等赵颅,每種原生數(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è)特性通常有兩方面用途:

      1. 由于Nothing函數(shù)不會(huì)正常終止访敌,因此如果能執(zhí)行到調(diào)用Nothing函數(shù)凉敲,那么其后面的代碼就不會(huì)被執(zhí)行到,這些代碼可被優(yōu)化掉:

        val addr = company.address ?: fail("No address")
        println(addr.city)
        

        如果company.addressnull寺旺,那么后續(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)行判空操作。

      2. 通常使用Nothing的這個(gè)特性用來(lái)聲明一些暫未完成的函數(shù):

        @kotlin.internal.InlineOnly
        public inline fun TODO(): Nothing = throw NotImplementedError()
        

      NothingUnit的區(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)容:

      1. null的類(lèi)型是Nothing?Nothing?是其他可空類(lèi)型的子類(lèi)型者春,這也就是為什么其他可空類(lèi)型的值可以設(shè)為null
      2. 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è)使用限制:

    1. const必須作用于頂層作用域蹈丸,或者object成員變量成黄,或者companion object成員變量。
    2. const必須以String類(lèi)型或基本數(shù)據(jù)類(lèi)型值進(jìn)行初始化逻杖。
    3. const不能設(shè)置自定義getter訪(fǎng)問(wèn)器奋岁。

建議優(yōu)先使用val聲明變量,無(wú)法滿(mǎn)足時(shí)再改為var荸百,這樣可以避免不經(jīng)意的數(shù)據(jù)修改闻伶,增加程序健壯性。

延遲初始化

Kotlin 支持對(duì)變量的延遲初始化够话,也即變量的懶加載虾攻,主要有如下兩種:

  • lateinitlateinit可以對(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):

    1. lalteinit只能作用于非空類(lèi)型變量,并且該變量不能是基本數(shù)據(jù)類(lèi)型景埃。
    2. 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)privateprotected屬性岭佳。

    :擴(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ù)為IntInt惭婿,返回值為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):可以理解為擁有gettersetter方法的字段

類(lèi)中方法的定義與普通函數(shù)定義格式一致祟峦,類(lèi)中屬性的定義完整格式如下:

// 成員變量
var <變量名>[: <類(lèi)型>] [= <初始化>]
    [<getter>]
    [<setter>]

// 成員常量
val <變量名>[: <類(lèi)型>] [= <初始化>]
    [<getter>]

舉個(gè)例子:為類(lèi)Human增加一些成員屬性:nameage徙鱼、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}"
    }
}

settergetter統(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ù)中使用valvar修飾夕玩,被valvar修飾的變量會(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)型延柠,比如SecondMinute或者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)支持繼承接口
  • 局部類(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)expressiontrue時(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ǔ)句主要有:continuebreakreturn前计,與其他語(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)strnull時(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)

  • runrun函數(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 最后一行些举。

  • withwith相對(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 最后一行)

  • letlet函數(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
    }
    
  • alsoalsolet基本一樣纵寝,除了返回值為調(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
    }
    
  • applyapply函數(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ū)別主要在于如下三方面:

  1. 都具備局部作用域
  2. 作用域的接收者為thisit
  3. 都具備返回值:返回值為 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ò)thisit的區(qū)別而已)酱畅,而如果想函數(shù)調(diào)用完成后,返回調(diào)用者自身江场,那么可以選擇alsoapply纺酸,如果想返回 Lambda 函數(shù)返回值,那么可選擇run址否、withlet餐蔬。
大多數(shù)情況下碎紊,使用run(或let)和apply即可滿(mǎn)足所有操作。

協(xié)程

具體內(nèi)容請(qǐng)參考:Kotlin - 協(xié)程 簡(jiǎn)介

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末樊诺,一起剝皮案震驚了整個(gè)濱河市仗考,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌词爬,老刑警劉巖秃嗜,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異顿膨,居然都是意外死亡锅锨,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)恋沃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)必搞,“玉大人,你說(shuō)我怎么就攤上這事囊咏」嘶” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵匆笤,是天一觀(guān)的道長(zhǎng)研侣。 經(jīng)常有香客問(wèn)我,道長(zhǎng)炮捧,這世上最難降的妖魔是什么庶诡? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮咆课,結(jié)果婚禮上末誓,老公的妹妹穿的比我還像新娘。我一直安慰自己书蚪,他們只是感情好喇澡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著殊校,像睡著了一般晴玖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上为流,一...
    開(kāi)封第一講書(shū)人閱讀 51,365評(píng)論 1 302
  • 那天呕屎,我揣著相機(jī)與錄音,去河邊找鬼敬察。 笑死秀睛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的莲祸。 我是一名探鬼主播蹂安,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼椭迎,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了田盈?” 一聲冷哼從身側(cè)響起侠碧,我...
    開(kāi)封第一講書(shū)人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎缠黍,沒(méi)想到半個(gè)月后弄兜,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瓷式,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年替饿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贸典。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡视卢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出廊驼,到底是詐尸還是另有隱情据过,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布妒挎,位于F島的核電站绳锅,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏酝掩。R本人自食惡果不足惜鳞芙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望期虾。 院中可真熱鬧原朝,春花似錦、人聲如沸镶苞。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)茂蚓。三九已至壕鹉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間煌贴,已是汗流浹背御板。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工锥忿, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留牛郑,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓敬鬓,卻偏偏與公主長(zhǎng)得像淹朋,于是被迫代替她去往敵國(guó)和親笙各。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

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

  • 1.變量與函數(shù) val:用于聲明不可變的變量础芍,這種變量在初始賦值之后就再也不能重新賦值杈抢,對(duì)應(yīng) Java 中的 fi...
    青年心路閱讀 546評(píng)論 0 0
  • 概述 Kotlin是面向?qū)ο蟮撵o態(tài)類(lèi)型語(yǔ)言; 在Kotlin中仑性,所有東西都是對(duì)象惶楼,在這個(gè)意義上可以在任意變量上調(diào)用...
    CodeMagic閱讀 393評(píng)論 0 0
  • 1. 重點(diǎn)理解val的使用規(guī)則 引用1 如果說(shuō)var代表了varible(變量),那么val可看成value(值)...
    leeeyou閱讀 511評(píng)論 0 0
  • 聲明诊杆,這里是我平時(shí)日常的筆記Zone歼捐,所以記錄可能會(huì)偏向于我認(rèn)為的重點(diǎn)區(qū)域,會(huì)有些疏漏或者缺失的地方晨汹,或是排版或者...
    哥哥是歐巴Vitory閱讀 837評(píng)論 0 2
  • 夜鶯2517閱讀 127,720評(píng)論 1 9