# 匿名函數

在前面的章節我們認識了函數，不過我們寫的函數都有名字。接下來我們會看到一種“沒有名字”的函數，我們把它稱之為匿名函數或是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)
    // 程式碼
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://kuroclass.gitbook.io/kotlin/ch7/ni-ming-han-shu.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
