目录
匿名函数
无参匿名函数
带参数的匿名函数
类型推断
定义参数是函数的函数
简略写法
函数内联
函数引用
函数类型作为返回类型
闭包
lambda与匿名内部类
我们在定义函数时,不给他名字,这个函数就是匿名函数了。匿名函数通常用法是作为参数传递给其他函数或者被其他函数返回。
Kotlin因有了匿名函数,我们开发者就可以轻松实现对kotlin标准库内置函数的定制。案例:
fun main() {
val a = "luffy".count()
val b = "luffy love honey".count({ letter ->
letter == 'y'
})
val c = "luffy love honey".count {
it == 'y'
}
println("a == $a")
println("b == $b")
println("b == $b")
}
匿名函数也是有类型,类型由传入的参数和返回值决定。匿名函数可以当作变量赋值给函数类型变量,就像其他变量一样,匿名函数也可以在代码中传递。和具名函数一样,匿名函数可以是无参数或接受一个或多个不同类型的参数。
fun main() {
//先定义匿名函数变量,noNameval是变量名,() -> String 表示该变量是无参数且返回值类型为String的匿名函数类型。
val noNameval: () -> String
//再赋值
noNameval = {
val holiday = "Mid-Autumn Festival"
"Happy $holiday"
}
//直接赋值
val noNameval1: () -> String = {
val holiday = "Mid-Autumn Festival"
"Happy $holiday"
}
println(noNameval())
println(noNameval1())
}
有小伙伴可能注意到了,上面案例中,匿名函数返回值类型是String,但是我并没有用return返回字符串,其实原因是匿名函数一般情况下会隐式返回最后一个语句的执行结果,而不需要我们用return关键字显式返回数据。
参数的类型放在匿名函数的类型定义中,参数名则放在函数定义中:
//带一个参数的匿名函数
val noNameval2: (String) -> String = { name ->
val holiday = "Mid-Autumn Festival"
"$name,Happy $holiday"
}
当匿名函数只要一个参数时,可以使用it关键字表示参数名:
//用it关键字改造
val noNameval3: (String) -> String = {
val holiday = "Mid-Autumn Festival"
"$it,Happy $holiday"
}
定义一个变量时,如果把匿名函数作为变量赋值给它,不需要显式指明变量类型,kotlin支持类型推断。
val noNameval4 = {
val holiday = "Mid-Autumn Festival"
"Happy $holiday"
}
类型推断也支持带参数的匿名函数,但为了帮助编译器更准确地推断变量类型,必须标明匿名函数的参数名和参数类型。
val noNameVal5 = { name: String, year: Int ->
val holiday = "Mid-Autumn Festival"
"$name,Happy $holiday,$year"
}
我们将匿名函数称为lambda,将它的定义称为lambda表达式,它返回的数据称为lambda结果。为什么叫lambda?lambda也可以用希腊字符 λ 表示,是lambda演算的简称,lambda是一套数理演算逻辑,是由数学家Alonzo Church(阿隆佐.丘齐)于20世纪30年代发明,在定义匿名函数时,使用了lambda演算记法。
即函数的参数是另外一个函数。
fun main() {
val discountWords = { goodsName: String, hours: Int ->
val currentYear = 2022
//给变量加上大括号{}之后,字符串就没有空格了
"${currentYear}年,${goodsName}双11大促销倒计时还有:$hours 小时"
}
showOnBoard("牙膏", discountWords)
}
// 具名函数
private fun showOnBoard(goodsName: String, discountWords: (String, Int) -> String) {
//返回一个1到24的随机数
val hour = (1..24).shuffled().last()
println(discountWords(goodsName, hour))
}
如果一个函数的lambda排在最后,或者是唯一的参数,那么lambda外面的圆括号可以省略掉。
fun main() {
//如果一个函数的lambda排在最后,或者是唯一的参数,那么lambda外面的圆括号可以省略掉
val a = "luffy".count { it == 'f' }
println("a = $a")
showOnBoard("牙膏") { goodsName: String, hours: Int ->
val currentYear = 2022
"${currentYear}年,${goodsName}双11大促销倒计时还有:$hours 小时"
}
}
//具名函数
private inline fun showOnBoard(goodsName: String, discountWords: (String, Int) -> String) {
//返回1到24的随机数
val hour = (1..24).shuffled().last()
println(discountWords(goodsName, hour))
}
lambda可以让你更灵活地编写应用,但是灵活也是要付出代价的。在JVM上,我们定义的lambda会以对象实例的形式存在,JVM会为所有同lambda打交道的变量分配内存,这就产生了内存开销,更糟的是,lambda的内存开销会带来严重的性能问题。幸运的是,kotlin有一种优化机制叫内联。有了内联,JVM就不需要使用lambda对象实例了,因而避免了变量内存分配。哪里需要使用lambda,编译器就会将函数体赋值粘贴到哪里,跟C语言中的宏定义比较类似,内联关键字是inline。
使用lambda的递归函数无法内联,因为会导致复制粘贴无限循环,编译器也会警告提示。
我们show kotlin bytecode,再把它反编译一下,就能看到lambda确实是以实例的形式存在:
使用内联之后,可以看到在有lambda的地方,JVM会帮我们复制粘贴lambda:
要把函数作为参数传递给其他函数使用,除了使用 lambda,kotlin还提供了函数引用的方式,函数引用可以把一个具名函数转换成一个值参,使用lambda的地方都可以使用函数引用。
fun main() {
showOnBoader("牙膏", ::getDiscountWords)
}
private fun showOnBoader(goodsName: String, discoutWords: (String, Int) -> String) {
val hours = (1..24).shuffled().last()
println(discoutWords(goodsName, hours))
}
private fun getDiscountWords(goodsName: String, hours: Int): String {
val currentYear = 2020
return "${currentYear}年双十一${goodsName}大促销倒计时:$hours 小时"
}
把函数类型作为一个函数的返回类型:
fun main() {
val getDiscountWords = configDiscountWords()
println(getDiscountWords("生活用品"))
// configDiscountWords()返回值是匿名函数,就把它看作是匿名函数,并且该匿名函数需要传入 String类型参数
val getDiscountWords1 = configDiscountWords()("生活用品")
println(getDiscountWords1)
}
private fun configDiscountWords(): (String) -> String {
return {
val currentYear = 2022
val hours = (1..24).shuffled().last()
"${currentYear}年,${it}双11大促销倒计时还有:${hours}小时"
}
}
在Kotlin中 ,匿名函数能修改并引用定义在自己的作用域之外的变量,匿名函数引用着定义自身的函数里的变量(作用域共享),可以这么理解,匿名函数就好比胎儿,定义匿名函数的函数就好比母体,母体的营养物质可供胎儿吸收使用。Koltlin中的lambda就是匿名函数,就是闭包,它们三个是一个东西。能接收函数或者返回函数的函数又叫高级函数,高级函数广泛应用于函数式编程中。
fun configDiscountWords(): (String) -> String {
var currentYear = 2020
val hours = (1..24).shuffled().last()
return {
//在kotlin中,匿名函数能够修改并引用定义在自身作用域之外的变量,即匿名函数可以引用定义自身的函数里面的变量
currentYear += 10
"${currentYear}年双十一${it}大促销倒计时:$hours 小时"
}
}
为什么要在代码中使用函数类型?原因就是函数类型可以让开发者少写模式化代码,写出更灵活的代码。Java8支持面向对象编程和lambda表达式,但不支持将函数作为参数传给另一个函数或变量,不过Java的替代方案是匿名内部类,可以看到多了好多模式化代码,比如必须先定义接口。
import java.security.SecureRandom;
/**
* @author luffy
* @description Java中的匿名内部类
*/
public class JavaAnonymousClass {
public static void main(String[] args) {
int year = 2022;
showOnBoard("生活用品", new DiscountGoods() {
@Override
public String getDiscountGoods(String goodsName, int hours) {
return String.format("%d年,%s双11大促销倒计时还有:%d小时", year, goodsName, hours);
}
});
}
interface DiscountGoods {
String getDiscountGoods(String goodsName, int hours);
}
public static void showOnBoard(String goodsName, DiscountGoods discountGoods) {
int hours = new SecureRandom().nextInt(24);
System.out.println(discountGoods.getDiscountGoods(goodsName, hours));
}
}
今天内容虽然不多,但对于初学Kotlin的同学还是有难度的,想一起学习交流的,私信我进粉丝群,也欢迎大家评论区留言讨论~