> For the complete documentation index, see [llms.txt](https://kuroclass.gitbook.io/kotlin/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://kuroclass.gitbook.io/kotlin/ch7/ni-ming-han-shu.md).

# 匿名函數

在前面的章節我們認識了函數，不過我們寫的函數都有名字。接下來我們會看到一種“沒有名字”的函數，我們把它稱之為匿名函數或是lambda函數。我們先來看看一個lambda函數的簡單例子

```kotlin
fun main() {
    println({ "Hello Kotlin!" }())
}
```

## lambda類型

lambda函數跟一般的函數一樣，有傳入0-多個東西然後傳出1個東西的功能，那我們來看看lambda函數的型態要怎麼表示：

```kotlin
fun main() {
    val helloFunction: () -> String = { "Hello Kotlin!" }
    println(helloFunction())
}
```

在這個範例裡面，我們把前面的lambda函數拆開來，並且設定給一個變數。我們知道變數只能裝符合型態的東西，所以我們必須了解lambda函數的型態是什麼。

### () -> String

lambda函數的型態我們會寫成這樣，從左到右依序是“(參數型態們)”、“ -> ”、“回傳型態”。用這樣的定義我們來看看上面的範例“() -> String”，指的就是這個lambda函數沒有參數，最後會回傳一個字串的意思。

### 隱式返回

在上面的lambda函數裡面有沒有發現雖然我們說是函數，但是最後並沒有return呢？其實在大多數情況下lambda函數並不需要使用return來回傳東西，lambda函數會回傳“**最後一句話的結果**”。

而為什麼return寫在這裡反而會錯呢？是因為這樣編譯器沒辦法判斷我們要返回的是哪一層的函數。所以假如我們真的有必要寫道return的話，我們可以幫lambda函數上“**標籤**”：

```kotlin
fun main() {
    val helloFunction: () -> String = hello@{ return@hello "Hello Kotlin!" }
    println(helloFunction())
}
```

利用標籤來讓編譯器知道我們要返回的是什麼函數，這樣編譯器就不會搞混了，而我們在後面會討論到有時候lambda函數會有“預設標籤”這一個特性。

## 參數

lambda函數既然也是函數，那就會有要傳入東西的需求，接下來我們就來看看下面的範例：

{% code title="ex. 6-2-1" %}

```kotlin
fun main() {
    val helloFunction: (String) -> String = { name ->
        "Hello $name!"
    }
    println(helloFunction("Kotlin"))
}
```

{% endcode %}

lambda函數的參數我們會寫在大括號的後面，然後再接一個箭頭(->)，而且傳入引數的方式也跟我們在呼叫一般函數的方式一樣。同樣的如果我們想要定義多個參數的話，也是一樣的方法：

```kotlin
fun main() {
    val helloFunction: (String, Int) -> String = { name, no ->
        "Hello $name! You are No.$no."
    }
    println(helloFunction("小黑", 7))
}
```

我們傳入了多個參數型態，這些我們全部寫進“( )”裡面並用“,”隔開，參數名字也是全部寫在箭頭(->)前面，並用“,”隔開

### it關鍵字

lambda函數有一個特別的關鍵字it，他只會出現在只有一個參數的lambda函數裡面。我們使用ex. 6-2-1的程式碼來示範

```kotlin
fun main() {
    val helloFunction: (String) -> String = { 
        "Hello $it!"
    }
    println(helloFunction("Kotlin"))
}
```

我們看到我們省略了明確的參數名字還有箭頭(->)，並且使用“it”來代替參數，這個是在只有一個一個參數的lambda函數裡面特別可以使用的。

## 把lambda函數當成參數

我們也可以把lambda函數當成另一個函數的參數，這樣可以增加方法的彈性：

```kotlin
fun main() {
    printFormattedMessage("Kotlin", { "Hello $it!" })
    printFormattedMessage("Android", { "Hi! $it!" })
    printFormattedMessage("クロさん", { "こんにちは! $it!" })
}

fun printFormattedMessage(name: String, formatFunction: (String) -> String) {
    println(formatFunction(name))
}
```

### 簡略語法

如果lambda函數是所有參數的最後一個的話，我們可以把lambda寫在“( )”後面：

```kotlin
fun main() {
    printFormattedMessage("Kotlin") { "Hello $it!" }
    printFormattedMessage("Android") { "Hi! $it!" }
    printFormattedMessage("クロさん") { "こんにちは! $it!" }
}

fun printFormattedMessage(name: String, formatFunction: (String) -> String) {
    println(formatFunction(name))
}
```

因為有這樣的簡略語法，通常我們在定義函數，只有一個參數是lambda函數的時候，我們就會把lambda函數放在參數的最後一個，並在呼叫的時候使用簡略語法。這樣字寫起來比較簡單易讀。

## 函數內聯

接下來我們來介紹內聯“inline”關鍵字，在Kotlin裡面我們會大量的使用lambda函數，這是程式語言提供的優勢，可是當程式碼編譯在JVM上的時候每一個lambda函數都會變成物件實例，這樣在做大量的資料處理的時候會對我們的效能產生很大的負擔。為了避免這樣的狀況我們可以使用“inline”關鍵字，使用inline關鍵字後，呼叫函數的時候並不會產生lambda函數的物件實例，而是會把函數的內容“貼上”一份在呼叫函數的地方，這樣可以增加效能。

不過內聯函數並不是萬用解，在某些時候並不能使用內聯函數，而通常這種時候編譯器也會提醒我們，比如：遞迴函數

## 函數參照

lambda函數很方便，可是如果我們想要使用現有的函數當作引數傳入的話要怎麼辦呢？

```kotlin
fun main() {
    printFormattedMessage("Kotlin", ::helloMessage)
}

fun printFormattedMessage(name: String, formatFunction: (String) -> String) {
    println(formatFunction(name))
}

fun helloMessage(name: String): String {
    return "Hello $name!";
}
```

這邊我們看到了新的運算子“::”，他可以把一個有名字的函數轉換成一個引數，讓我們可以把函數傳入函數裡面，只要是函數的型態跟要傳入的lambda函數一樣的時候都可以使用函數參照來傳入具名的函數。

## 使用lambda函數作為回傳值

lambda函數可以被當作參數，當然也可以當作回傳值，而且當我們看到下面的範例，我們可以發現lambda函數一些有趣的特性：

```kotlin
fun main() {
    val helloMessageFunction = configureHelloMessageFunction()
    println(helloMessageFunction("小黑"))
    println(helloMessageFunction("小固"))
    println(helloMessageFunction("白助"))
}

fun configureHelloMessageFunction(): (String) -> String {
    var number = 1
    return { "Hello $it! You're number is ${number++}." }
}
```

我們發現在執行的時候，回傳的lambda函數每被呼叫一次，同學的座號就會被加1。為什麼會這樣呢？其實當我們在使用lambda函數的時候，我們可以把每一次“實體化的lambda函數”當成是一個自己的小世界，在裡面取用的變數他的結果會被保留下來，所以就會造成這樣的結果。

## typealias

寫了這麼多lambda函數，有時候我們會覺得要寫這種“(......) -> ......”的型態我們會覺得很醜或是有時候我們看著看著就搞錯這個lambda函數是什麼作用。這個時候我們就可以使用“typealias”關鍵字來幫lambda函數命名，以上一個範例來說，我們可以改成這樣：

```kotlin
fun main() {
    val helloMessageFunction: HelloMessageFactory = configureHelloMessageFunction()
    println(helloMessageFunction("小黑"))
    println(helloMessageFunction("小固"))
    println(helloMessageFunction("白助"))
}

typealias HelloMessageFactory = (String) -> String

fun configureHelloMessageFunction(): HelloMessageFactory {
    var number = 1
    return { "Hello $it! You're number is ${number++}." }
}
```

## 練習

1. 定義一個函數可以自行決定學號的輸出格式並在main函數裡呼叫兩次，分別讓他輸出

* “TIP101-\_\_”
* “400420\_\_S”

```kotlin
fun printStudentId(number: Int, translation: (String) -> String) {
    // 把 數字 1 -> "01"
    val formattedNumber = String.format("%02d", number)
    // 程式碼
}
```
