Kotlin教程(八)高阶函数

写在开头:本人打算开始写一个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的类型推导:bash

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) }
复制代码

声明函数类型,须要将函数参数类型放在括号中,紧接着是一个箭头和函数的返回类型。app

你应该还记得Unit类型用于表示函数不返回任何有用的值。在声明一个普通的函数时,Unit类型的返回值是能够省略的,可是一个函数类型声明老是须要一个显式地返回类型,因此这里Unit是不能省略的。ide

在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表达式如何实现,以及内联函数中如何被内联的一样适用于匿名函数。

相关文章
相关标签/搜索