寫在開頭:本人打算開始寫一個Kotlin系列的教程,一是使自己記憶和理解的更加深刻,二是可以分享給同樣想學習Kotlin的同學。系列文章的知識點會以《Kotlin實戰》這本書中順序編寫,在將書中知識點展示出來同時,我也會添加對應的Java代碼用于對比學習和更好的理解。
Kotlin教程(一)基礎
Kotlin教程(二)函數
Kotlin教程(三)類、對象和接口
Kotlin教程(四)可空性
Kotlin教程(五)類型
Kotlin教程(六)Lambda編程
Kotlin教程(七)運算符重載及其他約定
Kotlin教程(八)高階函數
Kotlin教程(九)泛型
聲明高階函數
高階函數就是以另外一個函數作為參數或者返回值的函數。在Kotlin中,函數可以用lambda或者函數引用來表示。因此,任何以lambda或者函數引用作為參數的函數,或者返回值為lambda或函數引用的函數,都是高階函數。例如,標準庫中的filter函數將一個判斷式作為參數:
list.filter { x > 0 }
函數類型
為了聲明一個以lambda作為實參的函數,你需要知道如何聲明對應形參的類型。在這之前,我們先來看一個簡單的例子,把lambda表達式保存在局部變量中。其實我們已經見過在不聲明類型的情況下如何做到這一點,這依賴于Kotlin的類型推導:
val sum = { x: Int, y: Int -> x + y }
val action = { println(42) }
編譯器推導出sum和action這兩個變量具有函數類型。現在我們來看看這些變量的顯示類型聲明是什么樣子的:
val sum: (Int, Int) -> Int = { x, y -> x + y }
val action: () -> Unit = { println(42) }
聲明函數類型,需要將函數參數類型放在括號中,緊接著是一個箭頭和函數的返回類型。
你應該還記得Unit類型用于表示函數不返回任何有用的值。在聲明一個普通的函數時,Unit類型的返回值是可以省略的,但是一個函數類型聲明總是需要一個顯式地返回類型,所以這里Unit是不能省略的。
在lambda表達式{ x, y -> x + y }
中省略參數了類型,因為他們的類型已經在函數類型的變量聲明部分指定了,不需要在lambda本身的定義中再重復聲明。
就像其他方法一樣,函數類型的返回值也可以標記為可空類型:
var canReturnNull: (Int, Int) -> Int? = { null }
也可以定義一個函數類型的可空變量,為了明確表示是變量本身可空,而不是函數類型的返回類型可空,你需要將整個函數類型的定義包含在括號內并在括號后面添加一個問號:
var funOrNull: ((Int, Int) -> Int)? = null
注意這兩個例子的微妙區別。如果省略了括號,聲明的將會是一個返回值可空的函數類型,而不是一個可空的函數類型的變量。
函數類型的參數名
可以為函數類型聲明中的參數指定名字:
fun performRequest(
url: String,
callback: (code: Int, content: String) -> Unit //給函數類型的參數定義名字
) {
/*...*/
}
>>> val url = "http://kotl.in"
>>> performRequest(url) {code, content -> /*...*/} //可以使用定義的名字
>>> performRequest(url) {code, page -> /*...*/} //也可以改變參數名字
參數名稱不會影響類型的匹配。當你聲明一個lambda時,不必使用和函數類型聲明中一模一樣的參數名稱,但命名會提升代碼可讀性并且能用于IDE的代碼補全。
調用作為參數的函數
知道了怎樣聲明一個高階函數,現在我們拉討論如何去實現它。第一個例子會盡量簡單并且使用之前的lambda sum 同樣的聲明。這個函數實現兩個數字2和3的任意操作,然后打印結果。
fun twoAndThree(operation: (Int, Int) -> Int) {
val result = operation(2, 3)
println("The result is $result")
}
>>> twoAndThree { a, b -> a + b }
The result is 5
>>> twoAndThree { a, b -> a * b }
The result is 6
調用作為參數的函數和調用普通函數的語法是一樣的:把括號放在函數名后,并把參數放在括號內。
來看一個更有趣的例子,我們來實現最常用的標準庫函數:filter函數。為了讓事情簡單一點,將實現基于String類型的filter函數,但和作用與幾何的泛型版本的原理是相似的:
fun String.filter(predicate: (Char) -> Boolean): String {
val sb = StringBuilder()
for (index in 0 until length) {
val element = get(index)
if (predicate(element)) sb.append(element)
}
return sb.toString()
}
filter函數以一個判斷是作為參數,判斷是的類型是一個函數,以字符作為參數并返回Boolean類型的值。如果讓傳遞給判斷式的字符出現在最終返回的字符串中,判斷式需要返回true,反之返回false。
filter函數的實現非常簡單明了。它檢查每一個字符是否符合滿足判斷式,如果滿足就將字符添加到包含結果的StringBuilder中。
在Java中使用函數類
其背后的原理是,函數類型被聲明為普通的接口,一個函數類型的變量是FunctionN接口的一個實現。Kotlin標準庫定義了一系列的接口,這些接口對應于不同參數數量的函數:Function0<R>
沒有參數的函數、Function1<P1,R>
一個參數的函數等等。每個接口定義了一個invoke方法,調用這個方法就會執行函數。一個函數類型的變量就是實現了對應的FunctionN接口的實現類的實例,實現了類的invoke方法包含了lambda函數體。
在Java中可以很簡單的調用使用了函數類型的Kotlin。Java 8的lambda會被自動轉換為函數類型的值。
/*Kotlin定義*/
fun processTheAnswer(f: (Int) -> Int) {
println(f(42))
}
/*Java*/
>>> processTheAnswer(number -> number + 1)
43
在舊版的Java中,可以傳遞一個實現了函數接口中的invoke方法的匿名類的實例:
processTheAnswer(new Function1<Integer, Integer>() {
@Override
public Integer invoke(Integer integer) {
System.out.println(integer);
return integer + 1;
}
});
在Java中可以很容易地使用Kotlin標準庫中以lambda作為參數的擴展函數。但是要注意它們看起來并沒有Kotlin中name直觀——必須顯式地傳遞一個接收者對象作為第一個參數:
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
strings.add("42");
CollectionsKt.forEach(strings, new Function1<String, Unit>() {
@Override
public Unit invoke(String s) {
System.out.println(s);
return Unit.INSTANCE;
}
});
}
//輸出
42
在Java中,函數或者lambda可以返回Unit。但因為在Kotlin中Unit類型是有一個值的,所以需要顯式地返回它。
函數類型的參數默認值和null
聲明函數類型的參數的時候可以指定參數的默認值。要知道默認值的用處,我們回頭看一下教程二中joinToString函數,以下是它的最終實現:
fun <T> Collection<T>.joinToString(
separator: String = ",",
prefix: String = "",
postfix: String = ""
): String {
val result = StringBuilder(prefix)
for ((index, element) in this.withIndex()) {
if (index > 0) result.append(separator)
result.append(element)
}
result.append(postfix)
return result.toString()
}
這個實現很靈活,但是它并沒有讓你控制轉換的關鍵點:集合中元素是如何轉換為字符串的。代碼中使用了StringBuilder.append(o: Any?)
,它總是使用toString方法將對象轉換為字符串。在大多數情況下這樣就可以了,但并不總是這樣。為了解決這個問題,可以定義一個函數類型的參數并用一個lambda作為它的默認值。
fun <T> Collection<T>.joinToString(
separator: String = ",",
prefix: String = "",
postfix: String = "",
transform: (T) -> String = { it.toString() } //默認實現
): String {
val result = StringBuilder(prefix)
for ((index, element) in this.withIndex()) {
if (index > 0) result.append(separator)
result.append(transform(element)) //使用函數參數轉換
}
result.append(postfix)
return result.toString()
}
fun main(args: Array<String>) {
val letters = listOf("Alpha", "Beta")
println(letters.joinToString())
println(letters.joinToString { it.toLowerCase() })
println(letters.joinToString(separator = "! ", postfix = "! ", transform = { it.toUpperCase() }))
}
//輸出
Alpha,Beta
alpha,beta
ALPHA! BETA!
這個一個泛型函數:它有一個類型參數T表示集合中的元素的類型。transform將接收這個類型的參數。
聲明函數類型的默認值并不需要特殊的語法——只需要把lambda作為值放在=號后面。上面的例子展示了不同的函數調用方式:省略整個lambda(使用默認的toString做轉換),在括號以外傳遞lambda,或者以命名參數形式傳遞。
除了默認實現的方式來達到選擇性地傳遞,另一種選擇是聲明一個參數為可空的函數類型。注意這里不能直接調用作為參數傳遞進來的函數,需要先判空:
fun foo(callback: (() -> Unit)?){
if (callback != null) {
callback()
}
}
不想判空也是可以,利用函數類型是一個包含invoke方法的接口的具體實現。作為一個普通方法,invoke可以通過安全調用語法:callback?.invoke()
。
返回函數的函數
從函數中返回另一個函數并沒有將函數作為參數傳遞那么常用,但它仍然非常有用。想象一下程序中的一段邏輯可能會因為程序的狀態或者其他條件而產生變化——比如說,運輸費用的計算依賴于選擇的運輸方式。可以定義一個函數用來選擇恰當的邏輯變體并將它組委另一個函數返回。
enum class Delivery {STANDARD, EXPEDITED }
class Order(val itemCount: Int)
fun getShippingCostCalculator(delivery: Delivery): (Order) -> Double {
if (delivery == Delivery.EXPEDITED) {
return { order -> 6 + 2.1 * order.itemCount }
}
return { order -> 1.2 * order.itemCount }
}
>>> val calculator = getShippingCostCalculator(Delivery.EXPEDITED)
>>> println("Shipping costs ${calculator(Order(3))}")
Shipping costs 12.3
聲明一個返回另一個函數的函數,需要指定一個函數類型作為返回類型。getShippingCostCalculator返回了一個函數,這個函數以Order作為參數并返回一個Double類型的值。要返回一個函數,需要寫一個return表達式,跟上一個lambda、一個成員引用,或者其他的函數類型的表達式,比如一個函數類型的局部變量。
通過lambda去除重復代碼
函數類型和lambda表達式一起組成了一個創建可重用代碼的好工具。
我們來看一個分析網站訪問的例子,SiteView類用于保存每次訪問的路徑。持續時間和用戶的操作系統。不同的操作系統使用枚舉類型來表示:
enum class OS {WINDOWS, LINUX, MAC, IOS, ANDROID }
data class SiteVisit(val path: String, val duration: Double, val os: OS)
val log = listOf(SiteVisit("/", 34.0, OS.WINDOWS),
SiteVisit("/", 22.0, OS.MAC),
SiteVisit("/login", 12.0, OS.WINDOWS),
SiteVisit("/signup", 8.0, OS.IOS),
SiteVisit("/", 16.3, OS.ANDROID))
想象一下如果你需要顯示來自Windows機器的平均訪問時間,可以用average函數來完成這個任務:
val averageWindowsDuration =
log.filter { it.os == OS.WINDOWS }
.map(SiteVisit::duration)
.average()
>>> println(averageWindowsDuration)
23.0
現在假設你要計算來自Mac用戶的相同數據,為了避免重復,可以將平臺類型抽象為一個參數。
fun List<SiteVisit>.averageDurationFor(os: OS) =
filter { it.os == os }.map { it.duration }.average()
>>> println(log.averageDurationFor(OS.WINDOWS))
23.0
>>> println(log.averageDurationFor(OS.MAC))
22.0
將這個函數作為擴展函數增強了可讀性。如果它只在局部的上下文中有用,你甚至可以將這個函數聲明為局部擴展函數。
但這還遠遠不夠。想像一下,如果你對來自移動平臺的訪問的平均時間非常有興趣。
val averageMoblieDuration =
log.filter { it.os in setOf(OS.IOS, OS.ANDROID) }
.map(SiteVisit::duration)
.average()
>>> println(averageMoblieDuration)
12.15
現在已經無法再用一個簡單的參數表示不同的平臺了。你可能還需要使用更加復雜的條件查詢日志。比如,來自IOS平臺對注冊頁面的訪問的平均時間是多少?Lambda可以幫上忙,可以用函數類型將需要的條件抽象到一個參數中。
fun List<SiteVisit>.averageDurationFor(predicate: (SiteVisit) -> Boolean)
= filter(predicate).map(SiteVisit::duration).average()
>>> println(log.averageDurationFor {it.os in setOf(OS.ANDROID, OS.IOS)})
12.15
>>> println(log.averageDurationFor {it.os ==OS.IOS && it.path == "/signup"})
8.0
函數類型可以幫助去除重復代碼。如果你禁不住復制粘貼了一段代碼,那么很可能這段重復的代碼是可以避免的。使用lambda,不僅可以抽取重復的數據,也可以抽取重復的行為。
一些廣為人知的設計模式可以函數類型和lambda表達式進行簡化。比如策略模式。沒有lambda表達式的情況下,你需要聲明一個接口,并為沒一種可能的策略提供實現類,使用函數類型,可以用一個通用的函數類型來描述策略,然后傳遞不同的lambda表達式作為不同的策略。
內聯函數:消除lambda帶來的運行時開銷
lambda表達式會被正常編譯成匿名類。這表示沒調用一次lambda表達式,一個額外的類就會被創建。并且如果lambda捕捉了某個變量,那么每次調用的時候都會創建一個新的對象。這會帶來運行時的額外開銷,導致使用lambda比使用一個直接執行相同代碼的函數效率更低。
有沒有可能讓編譯器生成跟Java語句同樣高效的代碼,但還是能夠吧重復的邏輯抽取到庫函數中呢?是的,Kotlin的編譯器能做到。如果使用inline修飾符標記一個函數,在函數被使用的時候編譯器并不會生成函數調用的代碼,而是使用函數實現的真實代碼替換每一次的函數調用。
內聯函數如何運作
當一個函數被聲明為inline時,它的函數體是內聯的——換句話說,函數體會被直接替換到函數被調用的地方,而不是被正常調用。來看一個例子以便理解生成的最終代碼。
inline fun <T> synchronized(lock: Lock, action: () -> T): T {
lock.lock()
try {
return action()
} finally {
lock.unlock()
}
}
val l = Lock()
synchronized(l) {...}
這個函數用于確保一個共享資源不會并發地被多個線程訪問,函數鎖住一個Lock對象,執行代碼塊,然后釋放鎖。
調用這個函數語法根Java中使用synchronized語句完全一樣。區別在于Java的synchronized語句可以用于任何對象,而這個函數則要求傳入一個Lock實例。這里展示的定義只是一個示例,Kotlin標準庫中定義了一個可以接收任何對象作為參數的synchronized函數的版本。
因為已經將synchronized函數聲明為inline,所以每次調用它所生成的代碼跟Java的synchronized語句是一樣的。看看下面這個使用synchronized()的例子:
fun foo(l: Lock) {
println("Before sync")
synchronized(l) {
println("Action")
}
println("After sync")
}
下面的代碼將會編譯成相同字節碼:
fun __foo__(l: Lock) {
println("Before sync")
l.lock()
try {
println("Action")
} finally {
l.unlock()
}
println("After sync")
}
lambda表達式和synchronized函數的實現都被內聯了。由lambda生成的字節碼成為了函數調用者定義的一部分,而不是被包含在一個實現了函數接口的匿名類中。
注意,在調用內聯函數的時候也可以傳遞函數類型的變量作為參數:
class LockOwner(val lock: Lock) {
fun runUnderLock(body: () -> Unit) {
synchronized(lock, body)
}
}
在這種情況下,lambda的代碼在內聯函數被調用點是不可用的,因此并不會被內聯。只有synchronized的函數體被內聯了,lambda才會被正常調用。runUnderLock函數會被編譯成類似一下函數的字節碼:
class LockOwner(val lock: Lock) {
fun __runUnderLock__(body: () -> Unit) {
lock.lock()
try {
body() //body沒有被內聯,應為在調用的地方還沒有lambda
} finally {
lock.unlock()
}
}
}
如果兩個不同的位置使用同一個內聯函數,但是用的時不同的lambda,那么內聯函數會在每一個被調用的位置被分別內聯。內聯函數的代碼會被拷貝到使用它的兩個不同地方,并把不同的lambda替換到其中。
內聯函數的限制
鑒于內聯的運作方式,不是所有使用lambda的函數都可以被內聯。當函數被內聯的時候,作為參數的lambda表達式的函數體會被直接替換到最終生成的代碼中。這將限制函數體中對應lambda參數的使用。如果lambda參數被調用,這樣的代碼能被容易地內聯。但如果lambda參數在某個地方被保存起來,以便后面可以繼續使用,lambda表達式的代碼將不能被內聯,因為必須要有一個包含這些代碼的對象存在。
一般來說,參數如果被直接調用或者作為參數傳遞給另外一個inline函數,它是可以被內聯的。否則,編譯器會禁止參數被內聯并給出錯誤信息“Illegal usage of inline-parameter”。
例如,許多作用于序列的函數會返回一些類的實例,這些類代表對應的序列操作并接收lambda作為構造方法的參數。以下是Sequence.map函數的定義:
public fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R> {
return TransformingSequence(this, transform)
}
map函數沒有直接調用作為transform參數傳遞進來的函數。而是將這個函數傳遞給一個類的構造方法,構造方法將它保存在一個屬性中。為了支持這一點,作為transform參數傳遞的lambda需要被編譯成標準的非內聯的表示法,即一個實現了函數接口的匿名類。
如果一個函數期望兩個或更多lambda參數,可以選擇只內聯其中一些參數。這樣是有道理的,因為一個lambda可能會包含很多代碼或者以不允許內聯的方式使用。接收這樣的非內聯lambda的參數,可以用noline
修飾符來標記它:
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {}
編譯器完全支持內聯跨模塊的函數或者第三方庫定義的函數。也可以在Java中調用絕大部分內聯函數,但這些調用并不會被內聯,而是被編譯成普通的函數調用。
內聯集合操作
我們來仔細看一看Kotlin標準庫操作集合函數的性能。大部分標準庫中的集合函數都帶有lambda參數,相比于使用標準庫函數,直接實現這些操作不是更高效嗎?
例如,讓我們來比較以下兩個代碼中用來過濾一個人員列表的方式:
data class Person(val name: String, val age: Int)
val people = listOf(Person("Hubert", 26), Person("Bob", 31))
>>> println(people.filter{ it.age <= 30 })
[Person(name=Hubert, age=26)]
前面的代碼不用lambda表達式也可以實現:
val result = mutableListOf<Person>()
for (person in people) {
if (person.age <= 30) result.add(person)
}
println(result)
在Kotlin中,filter函數被聲明為內聯函數。這意味著filter函數,以及傳遞給他的lambda的字節碼會被一起內聯戴filter被調用的地方。最終,第一種實現所產生的字節碼和第二種實現所產生的字節碼大致是一樣的。你可以很安全地使用符合語言習慣的集合操作,Kotlin對內聯函數的支持讓你不必擔心性能問題。
想象一下現在你聯系調用filter和map兩個操作:
>>> println(people.filter { it.age > 30 }.map(Person::name))
[Bob]
這個例子使用了一個lambda表達式和一個成員引用。再一次,filter和map函數都被聲明為inline函數,所以它們的函數體會被內聯,因此不會產生額外的類或者對象。但是上面的代碼卻創建了一個中間集合來保存列表過濾的結果,由filter函數生成的代碼會向這個集合添加元素,而由map函數生成的代碼會讀取這個集合。
如果有大量集合元素需要處理,中間集合的運行開銷將成為不可忽視的問題,這時可以在調用鏈后加上一個asSquence調用,用序列來替代集合。但正如你在前面看到的,用來處理序列的lambda沒有被內聯。每一個中間序列被表示成把lambda保存在其字段中的對象,而末端操作會導致由每一個中間序列調用組成的調用鏈被執行。因此,即便序列上的操作是惰性的,你不應該總是試圖在集合操作的調用鏈后加上asSquence。這只在處理大量數據的集合時有用,曉得集合可以用普通的集合操作處理。
決定何時將函數聲明成內聯
inline雖然可以有效減少函數運行時開銷(包含減少匿名類的創建),但這是基于將標記的的函數拷貝到每一個調用點來達成的,因此,如果函數體的代碼過多,會增大字節碼的大小。考慮到JVM本身已經提供了強大的內聯支持:它會分析代碼的執行,并在任何通過內聯能夠帶來好處的時候將函數調用內聯。還有一點就是Kotlin的內聯函數在Java調用時并沒有其內聯的作用。最終,我們應該謹慎考慮添加inline,只將一些較小的,并且需要嵌入調用方的函數標記內聯。
高階函數中的控制流
當你開始使用lambda去替換像循環這樣的命令式代碼結構時,很快便發現遇到return表達式的問題。把一個return語句放在循環的中間是很簡單的事情。但是如果將循環轉換成一個類似filter的函數呢?在這種情況下return會如何工作?
lambda中的返回語句:從一個封閉的函數返回
來比較兩種不同的遍歷集合的方法。在下面的代碼中,很明顯如果一個的名字是Alice,就應該從函數lookForAlice返回:
fun main(args: Array<String>) {
val people = listOf(Person("Alice", 29), Person("Bob", 31))
lookForAlice(people)
}
data class Person(val name: String, val age: Int)
fun lookForAlice(people: List<Person>) {
for (person in people) {
if (person.name == "Alice") {
println("Found!")
return
}
}
println("Alice is not found")
}
//輸出
Found!
使用forEach迭代重寫這段代碼安全嗎?return語句還會是一樣的表現嗎?是的,正如下面的代碼展示的,forEach是安全的。
fun lookForAlice(people: List<Person>) {
people.forEach {
if (it.name == "Alice") {
println("Found!")
return
}
}
println("Alice is not found")
}
如果你在lambda中使用return關鍵字,它會從調用lambda的函數中返回,并不只是從lambda返回。這樣的return語句叫做非局部返回,因為它從一個比包含return的代碼塊更大的代碼塊中返回了。
為了理解這條規則背后的邏輯,想想Java函數中在for循環或者synchronized代碼塊中使用return關鍵字。顯然會從函數中返回,而不是從循環或者代碼塊中返回,當使用以lambda作為參數的函數的時候Kotlin保留了同樣的行為。
需要注意的是,只有在以lambda作為參數的函數是內聯函數的時候才能從更外層的函數返回。上面的例子中forEach的函數體和lambda的函數體一起被內聯了,所以在編譯的時候很容易做到從包含它的函數中返回。在一個非內斂函數的lambda中使用return表達式是不允許的。
從lambda返回:使用標簽返回
也可以在lambda表達式中使用局部返回。lambda中的局部返回跟for循環中的break表達式相似。它會終止lambda的執行,并接著從lambda的代碼處執行。要區分布局返回和非局部返回,要用到標簽。想從一個lambda表達式處返回你可以標記它,然后在return關鍵字后面引用這個標簽。
fun lookForAlice(people: List<Person>) {
people.forEach label@{ //聲明標簽
if (it.name == "Alice") {
return@label //返回標簽
}
}
println("Alice might be somewhere")
}
>>> lookForAlice(people)
Alice might be somewhere
要標記一個lambda表達式,在lambda的花括號之前放一個標簽名(可以是任何標識符),接著放一個@符號。要從lambda返回,在return關鍵字后放一個@符號,接著放標簽名。
或者默認使用lambda作為參數的函數的函數名作為標簽:
fun lookForAlice(people: List<Person>) {
people.forEach {
if (it.name == "Alice") {
return@forEach
}
}
println("Alice might be somewhere")
}
如果你顯式地指定了lambda表達式的標簽,在使用函數名作為標簽沒有任何效果。一個lambda表達式的標簽數量不能多以一個。
帶標簽的this表達式
同樣的規則也使用于this表達式的標簽。帶接收者的lambda包含一個隱式上下文對象的lambda可以通過this引用去訪問。如果你給帶接收者的lambda指定標簽,就可以通過對應的帶標簽的this表達式訪問它的隱式接收者。
println(StringBuilder().apply sb@ {
listOf(1, 2, 3).apply {
this@sb.append(this.toString())
}
})
和return表達式中使用標簽一樣,可以顯示地指定lambda表達式的標簽,也可以直接使用函數名作為標簽。
匿名函數:默認使用局部返回
匿名函數是一種不同的用于編寫傳遞給函數的代碼塊的方式。先來看一個示例:
fun lookForAlice(people: List<Person>) {
people.forEach(
fun(person) { //使用匿名函數取代lambda
if (person.name == "Alice") {
return
}
println("${person.name} is not Alice")
}
)
}
>>> lookForAlice(people)
Bob is not Alice
匿名函數看起來跟普通函數很相似,除了它的名字和參數類型被省略了外。這里有另外一個例子:
people.filter(fun(person): Boolean {
return person.age < 30
})
匿名函數和普通函數有相同的指定返回值類型的規則。代碼塊體匿名函數需要顯式地指定返回類型,如果使用表達式函數體,就可以省略返回類型。
people.filter(fun(person): Boolean = person.age < 30)
在匿名函數中,不帶標簽的return表達式會從匿名函數返回,而不是從包含匿名函數的函數返回。這條規則很簡單:return從最近使用fun關鍵字聲明的函數返回。lambda表達式沒有使用fun關鍵字,所以lambda中的return從最外層的函數返回。匿名函數使用了fun,因此,在前一個例子中匿名函數是最近的符合規則的函數。所以return表達式從匿名函數返回,而不是從最外層的函數返回。
注意,盡管匿名函數看起來跟普通函數很相似,但它其實是lambda表達式的另一種語法形式而已。關于lambda表達式如何實現,以及內聯函數中如何被內聯的同樣適用于匿名函數。