将函数当做参数或者是返回值的函数
可以看看我们常用的 forEach
函数:
1public inline fun <T> Array<out T>.forEach(action: (T) -> Unit): Unit {
2 for (element in this) action(element)
3}
首先我们可以知道, forEach
是 Array
的扩展函数,然后参数是 action
,但是 action
不再像和我们以前Java那样传递的是一个对象,这时传递的是一个函数 。这个函数的入参为 T
,返回值为 Unit
。所以 forEach
也是一个高阶函数,因为它将函数当做参数进行传递了。我们尝试着去调用一下 forEach
函数:
1fun main(args: Array<String>) {
2 args.forEach(::println)
3}
调用的时候,我们将 println
函数传递给了 forEach
函数,这里采用的是函数引用 。就上诉代码,我们还可以结合 Lambda
表达式来进行处理:
1fun main(args: Array<String>) {
2 args.forEach ({
3 println(it)
4 })
5 args.forEach{
6 println(it)
7 }
8 args.forEach(){
9 println(it)
10 }
11}
其实以上几种的方式得到的结果都是一样的,但是第一种就是简洁了许多。
我们再定义一个类,用来打印 forEach
的值:
1class PdfPrinter {
2 fun println(any: Any) {
3 kotlin.io.println(any)
4 }
5}
根据函数引用的特性,我们可以这样调用 println(any: Any)
函数:PdfPrinter::println
。由于 PdfPrinter
中的 println
函数的入参类型是 Any
类型,也就是任意类型,不管 forEach
传递的是什么值都可以接收。那现在我们再将其作为 forEach
的参数传递进去:
编译器告诉我们这个是错误的。那我们来分析一下吧:我们再定义一个 Hello
类
1class Hello {
2 fun world() {
3 println("Hello World.")
4 }
5}
然后进行以下操作:
可以看到 Android Studio
分别对 helloWorld
和 printer
的解释:
helloWorld
是一个方法,然后参数类型为 Hello
,返回值为 Unit
printer
也是一个方法,但是参数有两个,分别是 PdfPrinter
和 Any
类型, 返回值为 Unit
在 forEach
中,只有一个参数传递,但是 PdfPrinter::println
需要的是两个参数,肯定就会报错,所以我们需要对此进行修改:
1fun main(args: Array<String>) {
2 var pdfPrinter = PdfPrinter()
3 args.forEach(pdfPrinter::println)
4}
这个样子就OK了。
通常我们会使用以下的方式来实现对集合中的元素进行修改的操作:
1fun main(args: Array<String>) {
2 var list = listOf(1, 3, 4, 7, 89)
3 var newList = mutableListOf<Int>()
4 list.forEach {
5 var element = it * 2 + 1
6 newList.add(element)
7 }
8 newList.forEach(::println)
9}
10打印结果:
113
127
139
1415
15179
如果采用这种方式,远远不能体现Kotlin的优势了,这个和Java有什么区别呢?「狗子,上map」:
1fun main(args: Array<String>) {
2 var list = listOf(1, 3, 4, 7, 89)
3 var newList = list.map {
4 it * 2 + 1
5 }
6 newList.forEach(::println)
7}
8打印结果:
93
107
119
1215
13179
从打印结果可以看到他们的实现效果是一模一样的,这个就是 map
的功能,可以对集合中的元素进行你想要的操作,是不是跟 RxJava
的 map
很类似呢!我们来细看一下map
的实现原理:
1@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")
2public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
3 // 新创建一个ArrayList,默认长度是10,将ArrayList跟transform传递给mapTo
4 return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
5}
6
7public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.mapTo(destination: C, transform: (T) -> R): C {
8 // 进行迭代遍历
9 for (item in this)
10 // 将进行过变化的数据添加到新的集合中
11 destination.add(transform(item))
12 return destination
13}
map
方法中主要做的就是调用 mapTo
方法,然后传递的是新创建并且初始长度为10的 ArrayList
和 transform
函数,在 mapTo
方法中,对集合进行迭代,然后将进行变换后的数据添加到新的集合中,最后返回新的集合。
map
操作不仅可以将元素变换成与之前类型相同的元素,也可以变化成与之前元素类型不同的元素,具体你想变换成什么类型,这个是不做限制的。
1fun main(args: Array<String>) {
2 var list = listOf(1, 3, 4, 7, 89)
3 var newList = list.map(Int::toDouble)
4 newList.forEach(::println)
5}
6打印结果
71.0
83.0
94.0
107.0
1189.0
1fun main(args: Array<String>) {
2 var list = listOf(1..20, 3..50, 4..100)
3 var newList = list.flatMap {
4 it
5 }
6 newList.forEach(::println)
7}
8打印结果
91
102
11。。。
12100
flatMap
看起来跟 map
很相似,其实真的很类似,搞得有时候自己都不知道应该使用哪个操作符了,那就从源码来看看它们之间的区别吧。
1public inline fun <T, R> Iterable<T>.flatMap(transform: (T) -> Iterable<R>): List<R> {
2 // 新创建一个ArrayList,将ArrayList跟transform传递给mapTo
3 return flatMapTo(ArrayList<R>(), transform)
4}
5
6public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.flatMapTo(destination: C, transform: (T) -> Iterable<R>): C {
7 for (element in this) {
8 // 执行过transform以后的结果还是一个list集合,这里就是map和flatMap之间的区别了
9 val list = transform(element)
10 // 将变换后的集合整个添加到新的集合中
11 destination.addAll(list)
12 }
13 return destination
14}
可以看到 flatmap
中的参数 transform
是一个返回值为 Iterable<R>
的函数,而 map
的参数 transform
是一个返回值为 R
的函数。然后调用 flatMapTo
方法,将 transform
和一个新创建的 ArrayList
传递给了 flatMapTo
方法。在 flatMapTo
方法中,对当前的集合进行了迭代,然后将执行过变换操作后的集合数据全部添加到新的集合中,最终返回新的集合。
map
和 flatMap
的主要区别就是在于传入的函数的返回值,一个是任意对象,一个是实现了 Iterable
接口的对象
例子:打印集合中的元素之和
1fun main(args: Array<String>) {
2 var listOf = listOf(1, 2, 3, 4, 5, 6, 7)
3 var reduce = listOf.reduce { acc, i ->
4 println("$acc $i")
5 acc + i
6 }
7 println(reduce)
8}
9打印结果:
101 2
113 3
126 4
1310 5
1415 6
1521 7
1628
还是直接对源码进行分析吧,感觉看了源码就一目了然了。
1public inline fun <S, T: S> Iterable<T>.reduce(operation: (acc: S, T) -> S): S {
2 val iterator = this.iterator()
3 // 如果当前的集合为空就会抛出异常
4 if (!iterator.hasNext()) throw UnsupportedOperationException("Empty collection can't be reduced.")
5 // 首先将集合中的第一个元素赋值给accumulator
6 var accumulator: S = iterator.next()
7 while (iterator.hasNext()) {
8 // 执行operation,将accumulator和下一个元素传递给operation函数(也就是传递进行来的函数)
9 // 然后将返回结果赋值给accumulator
10 accumulator = operation(accumulator, iterator.next())
11 }
12 return accumulator
13}
首先对当前的集合进行判空处理,接着将第一个元素赋值给 accumulator
,accumulator
的类型是 S
。然后对当前集合进行迭代处理,并调用我们传递进去的参数 operation
,operation
函数中传递了两个参数,一个是 S
类型的,一个是集合元素类型的。operation
函数的返回值也是 S
类型的,将 operation
的返回值重新赋值给 accumulator
。迭代完毕以后返回我们的 accumulator
。
其实通过我们解读源码以后,我们就可以知道 reduce
函数会将上一次的计算结果传递到下一次计算中,我们可以利用这个方式来实现以下字符串拼接,当然我们的字符串拼接有其他更好的方式,这里只是做为讲解 reduce
的例子而已:
1fun main(args: Array<String>) {
2 var listOf = listOf("H", "e", "l", "l", "o", "W", "o", "r", "l", "d")
3 var reduce = listOf.reduce { acc, i ->
4 println("$acc $i")
5 "$acc$i"
6 }
7 println(reduce)
8}
9打印结果:
10H e
11He l
12Hel l
13Hell o
14Hello W
15HelloW o
16HelloWo r
17HelloWor l
18HelloWorl d
19HelloWorld
不得不说,fold
跟 reduce
的作用基本是一致的,只是 fold
能够添加初始值,什么叫做能够添加初始值呢?让我们来举个栗子看看呗!
1fun main(args: Array<String>) {
2 var listOf = listOf("H", "e", "l", "l", "o", "W", "o", "r", "l", "d")
3 var fold = listOf.fold("老铁说:") { acc, i ->
4 println("$acc $i")
5 "$acc$i"
6 }
7 println(fold)
8}
9打印结果:
10老铁说: H
11老铁说:H e
12老铁说:He l
13老铁说:Hel l
14老铁说:Hell o
15老铁说:Hello W
16老铁说:HelloW o
17老铁说:HelloWo r
18老铁说:HelloWor l
19老铁说:HelloWorl d
20老铁说:HelloWorld
还是看源码吧:
1public inline fun <T, R> Iterable<T>.fold(initial: R, operation: (acc: R, T) -> R): R {
2 // 将初始值赋值给 accumulator
3 var accumulator = initial
4 // 集合进行遍历操作,将accumulator和遍历到数据传递给 operation 函数,执行operation以后,
5 // 将operation函数的返回值有赋值给了accumulator,接着下一步的遍历
6 for (element in this) accumulator = operation(accumulator, element)
7 return accumulator
8}
看着源码就会觉得这些函数的操作很是简单了。fold
函数还有很多的兄弟:
foldRight
说的再多也不如看结果
1fun main(args: Array<String>) { 2 var listOf = listOf("H", "e", "l", "l", "o", "W", "o", "r", "l", "d") 3 // 注意!!!注意!!!注意!!!这边的参数跟fold函数调用的参数位置是相反的,具体原因可以看源码 4 var reduce = listOf.foldRight("老铁说:") { i, acc -> 5 println("$acc $i") 6 "$acc$i" 7 } 8 println(reduce) 9 10 val add5 = add(5) 11 println(add5(2)) 12} 13打印结果: 14老铁说: d 15老铁说:d l 16老铁说:dl r 17老铁说:dlr o 18老铁说:dlro W 19老铁说:dlroW o 20老铁说:dlroWo l 21老铁说:dlroWol l 22老铁说:dlroWoll e 23老铁说:dlroWolle H 24老铁说:dlroWolleHfoldRightIndexed
这个函数就是多了一个 index
的参数,具体的用处暂时没有发现,就不做数据打印了。例子:过滤集合中的奇数
1fun main(args: Array<String>) {
2 var listOf = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
3 var filter = listOf.filter { it % 2 == 0 }
4 filter.forEach(::println)
5}
6打印结果:
72
84
96
108
上源码:
1public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
2 // 新创建一个ArrayList,将ArrayList和predicate函数一起传递给filterTo
3 return filterTo(ArrayList<T>(), predicate)
4}
5
6public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterTo(destination: C, predicate: (T) -> Boolean): C {
7 // 为了保证代码跟源码的样式一致,这里就不做对代码的样式修改
8 // 对当前的数组进行遍历,如果满足predicate(element)条件,就将当前元素加入到新的集合中
9 for (element in this) if (predicate(element)) destination.add(element)
10 // 将新的集合返回
11 return destination
12}
在 filter
中创建新的集合 ArrayList
,将 ArrayList
和 predicate
函数一并传递给 filterTo
函数。在 filterTo
函数中,先对当前的集合进行遍历,如果满足条件 predicate(element)
就将当前的元素添加到新的集合中, predicate(element)
就是我们传递进来的那个函数,返回值是一个 Boolean
类型的。
例子:截取集合中不能够被5整除的数,直到第一个不满足条件的元素为止。
1fun main(args: Array<String>) {
2 var listOf = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9)
3 var takeWhile = listOf.takeWhile { it % 5 != 0 }
4 takeWhile.forEach(::println)
5}
6打印结果:
71
82
93
104
源码:
1public inline fun <T> Iterable<T>.takeWhile(predicate: (T) -> Boolean): List<T> {
2 // 新创建一个集合ArrayList
3 val list = ArrayList<T>()
4 // 遍历当前集合
5 for (item in this) {
6 if (!predicate(item))
7 // 如果不满足条件结束遍历,也不会将当前不满足条件的元素添加到新的集合中
8 break
9 // 将满足条件的元素添加到集合中
10 list.add(item)
11 }
12 // 返回新创建的集合
13 return list
14}
例子:省略if空判断
1fun main(args: Array<String>) {
2 // 获取一个Person对象,这个对象是可空的
3 var person = getPerson()
4 // 如果person为空,将不会调用let函数,也不会去执行后面的代码
5 // 如果不使用let函数的话,只能采取以下写法:
6 // if (person != null) {
7 // printPerson(person)
8 // println(person.name)
9 // println(person.age)
10 // }
11 // 当然这个例子真的只是例子,暂时还是不能区别出使用let方式的优势,只有用到复杂的逻辑实战中才能体现出
12 person?.let {
13 printPerson(person)
14 println(person.name)
15 println(person.age)
16 }
17}
18
19fun printPerson(person: Person){
20 // 操作person
21}
22
23fun getPerson(): Person? {
24 return null
25}
26
27class Person(var name: String, var age: Int) {
28}
源码:
1@kotlin.internal.InlineOnly
2public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
源码也是很简单,就是将调用者传递给传入进来的函数并执行传入进来的函数。
例子:修改person类的age属性
1fun main(args: Array<String>) {
2 var person = Person("laotie",18)
3 var apply = person?.apply {
4 this.age = 16
5 }
6 println(apply)
7}
8data class Person(var name: String, var age: Int)
上诉的例子真的只是例子,它没有跟你讲 apply
有多强大,它只是描述了的作用。源码:
1@kotlin.internal.InlineOnly
2public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
就是单纯的执行函数 block
并返回调用者。
例子:文件读取
1fun main(args: Array<String>) {
2 var reader = BufferedReader(FileReader("build.gradle"))
3 with(reader){
4 var line:String?
5 while (true){
6 // this 对象就是reader
7 line = this.readLine()?:break
8 println(line)
9 }
10 }
11}
12// 结果就不打印了
源码:
1@kotlin.internal.InlineOnly
2public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()
with
接收两个参数,一个是 receiver
,上诉例子中就是 reader
,另一个就是 函数
,在上诉例子中,我们使用了 Lambda
表达式,所以这个函数就移到了括号外面了。
我们一般定义函数都会选择定义
1fun <T> T.makeMoney2(block: () -> Unit) {
2 block()
3}
上诉代码表示:T
的扩展方法 makeMoney
接收一个 block
的函数,该函数是无参无返回的。
那我们再见识见识 T.() -> Unit
这种方式定义的方法,其实也很多见了:
1public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
可以看到 apply
函数使用的是 T.() -> Unit
这种方式,他们两到底有啥区别呢?
我在 ScrollingActivity
定义了两个方法:
1fun <T> T.makeMoney1(block: T.() -> Unit) {
2 block()
3}
4
5fun <T> T.makeMoney2(block: () -> Unit) {
6 block()
7}
分别使用 Button
调用这两个方法试试:
从图片中可以看出 :makeMoney1
中的 this
对象指的是调用对象 ,也就是 button
,而 makeMoney2
没有提示,那么我们就看打印吧:
1android.widget.Button{bb0caec VFED..C.. ......I. 0,0-0,0}
2cn.fengrong.kotlindemo.ScrollingActivity@c1cbf32
原来 makeMoney2
方法中的 this
对象指的是外部对象,在这里就是我们的ScrollingActivity
对象。
总结
这两个函数唯一的区别就是
T.()-Unit
与()->Unit
的区别,我们调用时,在代码块里面写this,的时候,根据代码提示,我们可以看到,连个this代表的含义不一样,T.()->Unit
里的this代表的是自身实例,而()->Unit
里,this代表的是外部类的实例
[Kotlin中,函数作为参数,T.()->Unit 和 ()->Unit 的区别][https://www.jianshu.com/p/88a656e59c61]