Kotlin Lambda编程
许多现代高级编程语言在很早之前就开始支持Lambda编程了,但是Java却直到JDK 1.8之后才加入了Lambda编程的语法支持。
而Kotlin从第一个版本开始就支持了Lambda编程,并且Kotlin中的Lambda功能极为强大,甚至认为Lambda才是Kotlin的灵魂所在。
集合的创建与遍历
传统意义上的集合主要就是List和Set,再广泛一点的话,像Map这样的键值对数据结构也可以包含进来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | // Kotlin专门提供了一个内置的listOf()函数来简化初始化集合的写法 // listOf()函数创建的是一个不可变的集合 val list = listOf( "Apple" , "Banana" , "Orange" , "Pear" , "Grape" ) for (fruit in list) { println(fruit) } // 创建一个可变的集合呢?也很简单,使用mutableListOf()函数就可以了 val list2 = mutableListOf( "Apple" , "Banana" , "Orange" , "Pear" , "Grape" ) list2.add( "Watermelon" ) for (fruit in list2) { println(fruit) } // Set集合的用法几乎与此一模一样,只是将创建集合的方式换成了setOf()和mutableSetOf()函数 val set = setOf( "Apple" , "Banana" , "Orange" , "Pear" , "Grape" ) for (fruit in set) { println(fruit) } //Map是一种键值对形式的数据结构,因此在用法上和List、Set集合有较大的不同。 val map = HashMap<String, Int>() map.put( "Apple" , 1 ) map.put( "Banana" , 2 ) map.put( "Orange" , 3 ) // 在Kotlin中并不建议使用put()和get()方法来对Map进行添加和读取数据操作,而是更加推荐使用一种类似于数组下标的语法结构 val map2 = HashMap<String, Int>() map2[ "Apple" ] = 1 map2[ "Banana" ] = 2 map2[ "Orange" ] = 3 // 当然,这仍然不是最简便的写法,因为Kotlin毫无疑问地提供了一对mapOf()和mutableMapOf()函数来继续简化Map的用法。 // 在mapOf()函数中,我们可以直接传入初始化的键值对组合来完成对Map集合的创建: val map3 = mapOf( "Apple" to 1 , "Banana" to 2 , "Orange" to 3 , "Pear" to 4 , "Grape" to 5 ) //这里的键值对组合看上去好像是使用to这个关键字来进行关联的,但其实to并不是关键字,而是一个infix函数 for ((fruit, number) in map3) { println( "fruit is " + fruit + ", number is " + number) } |
集合的函数式API
集合的函数式API有很多个,重点学习函数式API的语法结构,也就是Lambda表达式的语法结构。
Lambda的定义
如果用最直白的语言来阐述的话,Lambda就是一小段可以作为参数传递的代码。从定义上看,这个功能就很厉害了,因为正常情况下,我们向某个函数传参时只能传入变量,而借助Lambda却允许传入一小段代码。这里两次使用了“一小段代码”这种描述,那么到底多少代码才算一小段代码呢?Kotlin对此并没有进行限制,但是通常不建议在Lambda表达式中编写太长的代码,否则可能会影响代码的可读性。
Lambda表达式的语法结构
1 | {参数名 1 : 参数类型, 参数名 2 : 参数类型 -> 函数体} |
这是Lambda表达式最完整的语法结构定义。首先最外层是一对大括号,如果有参数传入到Lambda表达式中的话,我们还需要声明参数列表,参数列表的结尾使用一个->符号,表示参数列表的结束以及函数体的开始,函数体中可以编写任意行代码(虽然不建议编写太长的代码),并且最后一行代码会自动作为Lambda表达式的返回值。
简化的写法
当然,在很多情况下,并不需要使用Lambda表达式完整的语法结构,而是有很多种简化的写法。
1 2 3 4 5 6 7 8 9 10 11 12 13 | val list = listOf( "Apple" , "Banana" , "Orange" , "Pear" , "Grape" , "Watermelon" ) //这个Lambda参数是完全按照刚才学习的表达式的语法结构来定义的 val lambda = { fruit: String -> fruit.length } val maxLengthFruit = list.maxBy(lambda) //不需要专门定义一个lambda变量,而是可以直接将lambda表达式传入maxBy函数当中 val maxLengthFruit2 = list.maxBy({ fruit: String -> fruit.length }) //如果Lambda参数是函数的唯一一个参数的话,还可以将函数的括号省略: val maxLengthFruit3 = list.maxBy { fruit: String -> fruit.length } //由于Kotlin拥有出色的类型推导机制,Lambda表达式中的参数列表其实在大多数情况下不必声明参数类型 val maxLengthFruit4 = list.maxBy { fruit -> fruit.length } //当Lambda表达式的参数列表中只有一个参数时,也不必声明参数名,而是可以使用it关键字来代替 val maxLengthFruit5 = list.maxBy { it.length } println( "max length fruit is " + maxLengthFruit) |
其实maxBy就是一个普通的函数而已,只不过它接收的是一个Lambda类型的参数,并且会在遍历集合时将每次遍历的值作为参数传递给Lambda表达式。maxBy函数的工作原理是根据我们传入的条件来遍历集合,从而找到该条件下的最大值
接下来就再来学习几个集合中比较常用的函数式API
map函数
集合中的map函数是最常用的一种函数式API,它用于将集合中的每个元素都映射成一个另外的值,映射的规则在Lambda表达式中指定,最终生成一个新的集合。
1 2 3 4 5 6 7 | fun main() { val list = listOf( "Apple" , "Banana" , "Orange" , "Pear" , "Grape" , "Watermelon" ) val newList = list.map { it.toUpperCase() } for (fruit in newList) { println(fruit) } } |
filter函数
顾名思义,filter函数是用来过滤集合中的数据的,它可以单独使用,也可以配合刚才的map函数一起使用。
1 2 3 4 5 6 7 | fun main() { val list = listOf( "Apple" , "Banana" , "Orange" , "Pear" , "Grape" , "Watermelon" ) val newList = list.filter { it.length <= 5 }.map { it.toUpperCase() } for (fruit in newList) { println(fruit) } } |
any和all函数
其中any函数用于判断集合中是否至少存在一个元素满足指定条件,all函数用于判断集合中是否所有元素都满足指定条件。
1 2 3 4 5 6 | fun main() { val list = listOf( "Apple" , "Banana" , "Orange" , "Pear" , "Grape" , "Watermelon" ) val anyResult = list.any { it.length <= 5 } val allResult = list.all { it.length <= 5 } println( "anyResult is " + anyResult + ", allResult is " + allResult) } |
Java函数式API的使用
在Kotlin中调用Java方法时也可以使用函数式API,只不过这是有一定条件限制的。具体来讲,如果我们在Kotlin代码中调用了一个Java方法,并且该方法接收一个Java单抽象方法接口参数,就可以使用函数式API。Java单抽象方法接口指的是接口中只有一个待实现方法,如果接口中有多个待实现方法,则无法使用函数式API。
Java原生API中有一个最为常见的单抽象方法接口——Runnable接口。这个接口中只有一个待实现的run()方法,定义如下:
1 2 3 | public interface Runnable { void run(); } |
Thread类的构造方法中接收了一个Runnable参数,我们可以使用如下Java代码创建并执行一个子线程:
1 2 3 4 5 6 | new Thread( new Runnable() { @Override public void run() { System.out.println( "Thread is running" ); } }).start(); |
注意,这里使用了匿名类的写法,我们创建了一个Runnable接口的匿名类实例,并将它传给了Thread类的构造方法,最后调用Thread类的start()方法执行这个线程。
而如果直接将这段代码翻译成Kotlin版本,写法将如下所示:
1 2 3 4 5 | Thread(object : Runnable { override fun run() { println( "Thread is running" ) } }).start() |
Kotlin中匿名类的写法和Java有一点区别,由于Kotlin完全舍弃了new关键字,因此创建匿名类实例的时候就不能再使用new了,而是改用了object关键字。这种写法虽然算不上复杂,但是相比于Java的匿名类写法,并没有什么简化之处。
但是别忘了,目前Thread类的构造方法是符合Java函数式API的使用条件的,下面我们就看看如何对代码进行精简,如下所示:
1 2 3 | Thread(Runnable { println( "Thread is running" ) }).start() |
这段代码明显简化了很多,既可以实现同样的功能,又不会造成任何歧义。因为Runnable类中只有一个待实现方法,即使这里没有显式地重写run()方法,Kotlin也能自动明白Runnable后面的Lambda表达式就是要在run()方法中实现的内容。
另外,如果一个Java方法的参数列表中有且仅有一个Java单抽象方法接口参数,我们还可以将接口名进行省略,这样代码就变得更加精简了:
1 2 3 | Thread({ println( "Thread is running" ) }).start() |
不过到这里还没有结束,和之前Kotlin中函数式API的用法类似,当Lambda表达式是方法的最后一个参数时,可以将Lambda表达式移到方法括号的外面。同时,如果Lambda表达式还是方法的唯一一个参数,还可以将方法的括号省略,最终简化结果如下:
1 2 3 | Thread { println( "Thread is running" ) }.start() |
Android SDK还是使用Java语言编写的,当我们在Kotlin中调用这些SDK接口时,就很可能会用到这种Java函数式API的写法。
举个例子,Android中有一个极为常用的点击事件接口OnClickListener,其定义如下:
1 2 3 | public interface OnClickListener { void onClick(View v); } |
可以看到,这又是一个单抽象方法接口。假设现在我们拥有一个按钮button的实例,然后使用Java代码去注册这个按钮的点击事件,需要这么写:
1 2 3 4 5 | button.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { } }); |
而用Kotlin代码实现同样的功能,就可以使用函数式API的写法来对代码进行简化,结果如下:
1 2 | button.setOnClickListener { } |
最后提醒一句,本节中学习的Java函数式API的使用都限定于从Kotlin中调用Java方法,并且单抽象方法接口也必须是用Java语言定义的。你可能会好奇为什么要这样设计。这是因为Kotlin中有专门的高阶函数来实现更加强大的自定义函数式API功能,从而不需要像Java这样借助单抽象方法接口来实现。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
2021-01-13 MySQL MyISAM表锁
2021-01-13 MySQL锁概述
2019-01-13 springboot集成mybatis
2019-01-13 idea搭建springboot
2017-01-13 mui 对话框 点击按钮不关闭对话框的办法
2017-01-13 CSS 背景(css background)