kotlin中級篇

函數和lambda表達式

  • 函數聲明
fun double(x: Int): Int {
    return 2*x
}
  • 函數調用
val result = double(2)
 Sample().foo() // 創建類 Sample的對象,并調用foo()函數
  • 默認參數
fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) {
    ......
}

read("ab".toByteArray().toTypedArray())
  • 命名參數
fun reformat(str: String,
             normalizeCase: Boolean = true,
             upperCaseFirstLetter: Boolean = true,
             divideByCamelHumps: Boolean = false,
             wordSeparator: Char = ' ') {

    println("str:$str, " +
            "normalizeCase:$normalizeCase, " +
            "upperCaseFirstLetter:$upperCaseFirstLetter, " +
            "divideByCamelHumps:$divideByCamelHumps, " +
            "wordSeparator:$wordSeparator")
}

reformat("hello",
            normalizeCase = true,
            divideByCamelHumps = false,
            wordSeparator = '_')

reformat("hello", wordSeparator = '-')
  • 可變數量的參數(Varargs)
    函數的參數(通常是最后一個)可以用 vararg 修飾符標記:
fun <T> asList(vararg ts: T): List<T> {
    val result = ArrayList<T>()
    for (t in ts) // ts is an Array
        result.add(t)
    return result
}

val list = asList(1, 2, 3)

val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)

* 可以將數字或集合展開

  • 局部函數
    局部函數可以訪問外部函數(即閉包)的局部變量visited
fun dfs(graph: Graph) {
    val visited = HashSet<Vertex>()
    fun dfs(current: Vertex) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
            dfs(v) 
    }
    dfs(graph.vertices[0])
}

lambda表達式和匿名函數

一個 lambda 表達式或匿名函數是一個函數類型的字面值,即一個未聲明的函數, 但立即做為表達式傳遞??紤]下面的例子:

max(strings, { a, b -> a.length < b.length })

函數 max 是一個高階函數,換句話說它接受一個函數作為第二個參數。 其第二個參數是一 個表達式,它本身是一個函數,即函數字面值。寫成函數的話,它相當于

 fun compare(a: String, b: String): Boolean = a.length < b.length

函數類型

fun <T> max(collection: Collection<T>, less: (T, T) -> Boolean): T? {
    var max: T? = null
    for (it in collection)
        if (max == null || less(max, it))
            max = it
    return max 
}

在上面的 max 函數中參數less 的類型是 (T, T) -> Boolean,即一個接受兩個類型 T 的參數并返回一個布爾值的函數,參數less(max, it) 作為一個函數來調用,就是函數類型?;蛘呖梢杂懈Z義化的參數名稱:

less: (left: T, right: T) -> Boolean

lamda表達式

  • 函數類型的字面值
  • lambda 表達式總是被大括號括著
  • 其參數(如果有的話)在 -> 之前聲明(參數類型可以省略)
  • 函數體(如果存在的話)在 -> 后面
  • 返回值,如果推斷出的該lambda的返回類型不是 Unit ,那么該lambda主體中的最后一個表達式會視為返回值
val compare: (T, T) -> Boolean = { a, b -> a.length < b.length }

在 Kotlin 中有一個約定,如果函數的最后一個參數是一個函數,并且你傳遞一個 lambda 表達 式作為相應的參數,你可以在圓括號之外指定它:

max(strings) { a, b -> a.length < b.length }

請注意,如果 lambda 是該調用的唯一參數,則調用中的圓括號可以完全省略。

  val doubled = ints.map { value -> value * 2 }

it :單個參數的隱式名稱

 ints.map { it * 2 }

下劃線用于未使用的變量

map.forEach { _, value -> println("$value!") }

限定的返回語法從lambada顯示返回一個值,否則將隱式的返回最后一個表達式的值,因此一下片段是等價的:

ints.filter {
    val shouldFilter = it > 0
    shouldFilter
}
ints.filter {
    val shouldFilter = it > 0
    return@filter shouldFilter
}

匿名函數
上面提供的 lambda 表達式語法缺少的一個東西是指定函數的返回類型的能力。在大多數情況 下,這是不必要的。因為返回類型可以自動推斷出來。然而,如果確實需要顯式指定,可以 使用另一種語法: 匿名函數 。

 fun(x: Int, y: Int): Int = x + y

 //或者下面的形式
 fun(x: Int, y: Int): Int {
    return x + y
}

匿名函數看起來非常像一個常規函數聲明,唯一的區別是省略了名稱。

請注意,匿名函數參數總是在括號內傳遞。 允許將函數留在圓括號外的簡寫語法僅適用于 lambda 表達式。
Lambda表達式和匿名函數之間的另一個區別是非局部返回的行為。一個不帶標簽的 return 語句總是在用 fun 關鍵字聲明的函數中返回。這意味著 lambda 表達式中的 return 將從包 含它的函數返回,而匿名函數中的 return 將從匿名函數自身返回。

為了方便記憶,我是這么理解函數類型的,函數類型函數字面值的關系:函數類型就好比java的接口,是一個函數的模板,而函數字面值好比函數對象。ps:完全是個人的理解,你也可以有你自己的理解。

  • 高階函數
    高階函數是將函數用作參數或返回值的函數。下面的lock()函數接受一個鎖對象和一個函數,獲取鎖,運行函數并釋放鎖:
fun <T> lock(lock: Lock, body: () -> T): T {
    lock.lock()
    try {
        return body()
} finally {
        lock.unlock()
    }
} 

在上面的函數中,body 擁有函數類型: () -> T , 所以它應該是一個不帶參數并且返回 T 類型值的函數。它在 try -代碼塊內部調用、被 lock 保護,其結果由 lock() 函數返回。

如果我們想調用 lock() 函數,我們可以把另一個函數傳給它作為參數:

fun toBeSynchronized() = sharedResource.operation()
val result = lock(lock, ::toBeSynchronized)

通常會更方便的另一種方式是傳一個 lambda 表達式:

val result = lock(lock, { sharedResource.operation() })

高階函數的另一個例子是 map() :

fun <T, R> List<T>.map(transform: (T) -> R): List<R> {
    val result = arrayListOf<R>()
    for (item in this)
        result.add(transform(item))
    return result
}

val doubled = ints.map { value -> value * 2 }
  • 內聯函數

使用高階函數會帶來一些運行時的效率損失:每一個函數都是一個對象,并且會捕獲一個閉 包。 即那些在函數體內會訪問到的變量。 內存分配(對于函數對象和類)和虛擬調用會引入 運行時間開銷。
但是在許多情況下通過內聯化 lambda 表達式可以消除這類的開銷。
上面的lock() 函數可以很容易地在調用處內聯。 考慮下面的情況:

 lock(l) { foo() }

編譯器沒有為參數創建一個函數對象并生成一個調用。取而代之,編譯器可以生成以下代碼:

l.lock()
try {
    foo() 
}
finally {
    l.unlock()
}

為了讓編譯器這么做,我們需要使用 inline 修飾符標記 lock() 函數:

inline fun lock<T>(lock: Lock, body: () -> T): T {
    // ......
}

內聯可能導致生成的代碼增加,但是如果我們使用得當(不內聯大函數),它將在性能上有所提升,尤其是在循環中的“超多態(megamorphic)”調用處。
部分內聯,noinline 修飾符標記某個函數參數禁用內聯:

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
    // ......
}
  • 擴展函數

聲明一個擴展函數,我們需要用一個 接收者類型 也就是被擴展的類型來作為他的前綴。 下面
代碼為 MutableList<Int> 添加一個 swap 函數:

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

val l = mutableListOf(1, 2, 3)
l.swap(0, 2) // “swap()”內部的“this”得到“l”的值

擴展是靜態解析的
擴展不能真正的修改他們所擴展的類。通過定義一個擴展,你并沒有在一個類中插入新成 員, 僅僅是可以通過該類型的變量用點表達式去調用這個新函數如下所示:

open class C

class D: C()

fun C.foo() = "c"

fun D.foo() = "d"

fun printFoo(c: C) {
    println(c.foo())
}

printFoo(D())

上面將輸出 c ,你也可以理解為擴展函數不具備多態的功能。
優先調用成員函數,如果一個類定義有一個成員函數和一個擴展函數,而這兩個函數又有相同的接收者類型、相同的名字并且都適用給定的參數,這種情況總是取成員函數:

class C {
    fun foo() { println("member") }
}
fun C.foo() { println("extension") }

C().foo() //將輸出``member``

擴展的作用域
大多數時候我們在頂層定義擴展,即直接在包里:

package foo.bar
fun Baz.goo() { ...... }

要使用所定義包之外的一個擴展,我們需要在調用方導入它:

package com.example.usage

import foo.bar.goo // 導入所有名為“goo”的擴展 
                                    // 或者
import foo.bar.* // 從“foo.bar”導入一切

fun usage(baz: Baz) {
    baz.goo()
}
  • 尾遞歸函數

Kotlin 支持一種稱為尾遞歸的函數式編程風格。 這允許一些通常用循環寫的算法改用遞歸函 數來寫,而無堆棧溢出的風險。 當一個函數用 tailrec 修飾符標記并滿足所需的形式時,編譯器會優化該遞歸,留下一個快速而高效的基于循環的版本。

tailrec fun findFixPoint(x: Double = 1.0): Double
        = if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))

上面的代碼等價于下面的代碼:

private fun findFixPoint(): Double {
    var x = 1.0
    while (true) {
        val y = Math.cos(x) 
        if (x == y)
            return y 
        x = y
    } 
}

尾遞歸的條件:

  1. 調用自身的操作必須是最后一個執行。
  2. 不能用在 try/catch/finally 塊中。

注意目前只在 JVM 后端中支持

您如需了解kotlin更高級的特性如:反射、協程、類型安全的構建器、注解,請看:kotlin高級篇

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容