*匿名函数
定义时不取名字的函数,我们称之为匿名函数。匿名函数通常整体传递给其他函数,或者从其他函数返回。匿名函数对 Kotlin 来说很重要,有了它,我们能够根据需要制定特殊规则,轻松定制标准库里的内置函数。
示例:
fun main(){
val count = "Mississippi".count()
println(count)
val countS = "Mississipi".count({letter ->
letter == 's'
})
println(countS)
}
String.count() 中 count() Kt 中自带的 API,可以统计字符串中字符的个数。而匿名函数就可以给这些自带的标准函数(API)制定一些特殊的规则。比如,我们上面统计字符 's' 出现的次数,就可以在 count() 的括号里传一个匿名函数,即 "{letter -> letter == 's'}"
函数类型与隐式返回
匿名函数也有类型,匿名函数可以当作变量赋值给函数类型变量,就像其他变量一样,匿名函数就可以在代码里传递了。变量有类型,变量可以等于函数,函数也会有类型。函数的类型,由传入的参数和返回值类型决定。
和具名函数不一样,除了极少数情况外,匿名函数不需要 return 关键字来返回数据,匿名函数会隐式或自动返回函数体最后一行语句的结果。
示例:
fun main(){
// 变量的类型是一个匿名函数,即 冒号 后面的部分
val blessingFunction : () -> String = {
val holiday = "New Year."
"Happy $holiday"
}
println(blessingFunction)
}
解释说明:blessingFunction 就是一个变量,这个变量的类型是一个函数的类型。因为当我们定义一个变量时一般是: val blessingFunction : String,所以冒号“:” 后面的内容就是它的类型,即这里一个函数 () -> String(这个函数无参,返回值为 String)。只要是任意一个匿名函数,它没有传入参数,返回类型是 String 类型的,都可以等于 blessingFunction 这个变量。而这里我们又用一个匿名函数来赋值,即
{
val holiday = "New Year."
"Happy $holiday"
}
这个匿名函数没有参数,且返回类型为最后一行的结果,即 String。
匿名函数参数
和具名函数一样,匿名函数可以不带参数,也可以带一个或多个任何类型的参数。需要带参数时,参数的类型放在匿名函数的类型定义中,参数名则放在函数定义中。
示例1:一个 Int 参数
fun main(){
val number : (Int) -> Int = {original ->
val num = 1
num + 1 + original
}
println("The result number is " + number(2))
}
解释说明:
示例2:两个参数,一个 String 的 name,一个 Int 的 age
fun main(){
val person : (String, Int) -> String = {name, age ->
"His name is $name, and age is $age"
}
println(person("HL", 17))
}
it 关键字
定义只有一个参数的匿名函数时,可以使用 it 关键字来表示参数名。当你需要传入两个参数时,it 关键字就不能用了。
示例:用 it 代替 original
类型推断
定义一个变量时,如果已把匿名函数作为变量赋值给它,就不需要显示指明变量类型了。
省略了 :() -> String 。
类型推断也支持带参数的匿名函数,但为了帮助编译器更准确地推断变量类型,匿名函数的参数名和参数类型必须有。
lambda
我们将匿名函数称为 lambda,将它的定义称为 lambda 表达式,它返回的数据称为 lambda 结果。为什么叫 lambda?lambda 也可以用希腊字符λ表示,是 lambda 演算的简称,lambda 演算是一套数理演算逻辑,由数学家 Alonzo Church(阿隆佐.丘齐)于20世纪30年代发明,在定义匿名函数时,使用了 lambda 演算记法。
定义参数是函数的函数
函数的参数是另外一个函数。可以理解为把一个匿名函数作为一个具名函数的参数传入。
简略写法
如果一个函数的 lambda 参数排在最后,或者是唯一的参数,那么括住 lambda 值参的一对圆括号就可以省略。如下代码所示:在编译器里,黄线部分就是提示我们可以省略圆括号。因为只有一个参数,且还是 lambda
黄线消失
示例2:
上面代码的最后一个参数是 lambda, 那么括住 lambda 值参的圆括号就不需要了,因为这里 lambda 不是唯一的参数,所以正确的写法是放到圆括号外面,如下代码所示
示例3:如果只有一个参数,无论是普通参数,还是 lambda 参数都可以省略圆括号
注意:上面代码的省略写法很容易让人误解、看不懂。所以需要多理解 Kt 里的这种简略写法。
函数内联
lambda 可以让你更灵活地编写应用,但是,灵活也是要付出代价的。在 JVM 上,你定义的 lambda 会以对象实例的形式存在,JVM 会为所有同 lambda 打交道的变量分配内存,这就产生了内存开销。更糟的是,lambda 的内存开销会带来严重的性能问题。幸运的是,kotlin 有一种优化机制叫内联,有了内联,JVM 就不需要使用 lambda 对象实例了,因而避免了变量内存分配。哪里需要使用 lambda,编译器就会将函数体复制粘贴到哪里。使用 lambda 的递归函数无法内联,因为会导致复制粘贴无限循环,编译会发出警告。
函数引用
要把函数作为参数传给其他函数使用,除了传 lambda 表达式,kotlin 还提供了其他方法,传递函数引用,函数引用可以把一个具名函数转换成一个值参,使用 lambda 表达式的地方,都可以使用函数引用。
示例:
解释说明:personInfo 是一个具名函数,而 getPerson 的第二个参数是匿名函数,所以通过函数引用(::函数名)就可以把一个具名函数传进去。
函数类型作为返回类型
函数类型也是有效的返回类型,也就是说可以定义一个能返回函数的函数。
示例:
fun main(){
val personInfo = getPersonInfo()
println(personInfo("HL", 17))
}
fun getPersonInfo() : (String, Int) -> String {
val sex = "男"
return {name : String, age : Int ->
"His name is $name, and he is $age years old and $sex."
}
}
解释说明:
注意:参数的个数和类型要对应。
闭包
在 kotlin 中,匿名函数能修改并引用定义在自己的作用域之外的变量,匿名函数引用着定义自身的函数里的变量,Kotlin 中的 lambda 就是闭包。前面提到,匿名函数就是 lambda,lambda 就是闭包。
能接收函数或者返回函数的函数又叫高级函数,高级函数广泛应用于函数式编程当中。
lambda 与匿名内部类
为什么要在代码中使用函数类型?函数类型能让开发者少写模式化代码,写出更灵活的代码。Java 8 支持面向对象编程和 lambda 表达式,但不支持将函数作为参数传给另一个函数或变量,不过 Java 的替代方案是匿名内部类。
示例:Java 通过匿名内部类传递函数
这段代码还没有用到 Java 的匿名内部类,而是通过我们通常熟悉的,写一个 MyPerson 类继承自 PersonInfo 接口,然后在 main() 方法中调用 getPersonInfo 时传一个 MyPerson 对象作为参数。
解释说明:这段代码相比较上面的传统写法,不再需要一个 MyPerson 类来实现 PersonInfo 接口。在 调用 getPersonInfo() 方法时,直接 new PersonInfo 作为参数传递。 这种写法中,PersonInfo 接口也是有实现类的,只是它的实现类的名字是匿名的,所以叫做匿名内部类。
上面 Java 中两种将函数作为参数的写法,都有模式化代码,即写接口的那一部分。
而在 Kt 中则完全不需要了,通过匿名函数即可实现,例如上面提到的定义参数是函数的函数、简略写法部分。