Kotlin杂谈系列七
-
这节就主要谈谈kotlin内置的迭代器
内部迭代器的优势: 避免的显示可变性的威胁 不会出现竞态条件的风险
内部迭代器就是高阶函数 高阶函数用来提供通用的代码 而传进来的lambda表达式则是执行特定的任务
-
主要的迭代器 filter() map() reduce()
filter : 传进去的lambda的表达式作为条件 满足条件的留下 不满足的过滤掉
val nums = listOf(1,2,4,5) fun main() { nums.filter { it>=3 }.forEach(::println) } //输出: 4 5
-
大致的流程就是lambda表达式接收的一个参数这个参数就是集合里的元素
-
看看filter的源码吧
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> { return filterTo(ArrayList<T>(), predicate) } public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterTo(destination: C, predicate: (T) -> Boolean): C { for (element in this) if (predicate(element)) destination.add(element) return destination }
- 通过源码可以发现 (T) -> Boolean lambda表达式是一个接收一个泛型参数返回布尔表达式的 具体来说这里的参数就是每个元素
-
map : 转换集合 将原来集合的值转换成一些其他的值
val nums = listOf(1,2,4,5) fun main() { nums.map { it*3 }.forEach(::println) } //输出 : 3 6 12 15
- 可以看到返回的集合原来集合的三倍
看看map的源码
public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> { return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform) } public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.mapTo(destination: C, transform: (T) -> R): C { for (item in this) destination.add(transform(item)) return destination }
- destination.add(transform(item)) 注意这段代码在添加一个新的元素的时候 以原来的元素为参数调用了我们传进来的lambda表达式 并将我们表达式的结果放入一个新的集合里
-
reduce : 他的意思是累加 和其他两个迭代器不同他的lambda的参数个数有两个 一个是累加值,第二个参数则是原始集合里的元素
val nums = listOf(1,2,4,5) fun main() { nums.reduce{num1,num2 -> num1+num2}.run(::println) } //输出 : 12
- 这个实现的功能是累和
看源码解释
public inline fun <S, T : S> Iterable<T>.reduce(operation: (acc: S, T) -> S): S { val iterator = this.iterator() if (!iterator.hasNext()) throw UnsupportedOperationException("Empty collection can't be reduced.") var accumulator: S = iterator.next() /*注意这里*/ while (iterator.hasNext()) { accumulator = operation(accumulator, iterator.next())/*注意这里*/ } return accumulator }
- 累和值的起始值是第一个迭代器值 var accumulator: S = iterator.next()
- 在后面的循环中 : accumulator = operation(accumulator, iterator.next()) 不断的将函数值赋给accumulator 我写的代码里 就相当于 accumulator = accumulator + iterator.next() 这就是累和功能
- 最后就返回这个累和值
- kotlin内置了一些专用的reduce函数 sum() max() 以及字符串的join方法
val nums = listOf(1,2,4,5) fun main() { nums.sum().run(::println) nums.maxOf { it }.run(::println) } //输出 : 12 5
-
-
flatten : 让嵌套的结构变成平面的结构
val nums = listOf(listOf(1,2), listOf(3,4), listOf(5,6)) fun main() { nums.size.run(::println) nums.forEach(::println) //上下对比 nums.flatten().size.run(::println) nums.flatten().run(::println) } //输出 : 3 [1, 2] [3, 4] [5, 6] 6 [1, 2, 3, 4, 5, 6]
-
可以看到确实是将嵌套的结构变成了平面的结构了
-
看看他的源码吧
public fun <T> Iterable<Iterable<T>>.flatten(): List<T> { val result = ArrayList<T>() /*关键*/ for (element in this) { result.addAll(element) /*关键*/ } return result }
- 可以看到在内部保存一个空的集合然后循环的将每个子集合的元素全部加进来 result.addAll(element) 这段代码就是这个意思
-
-
flatMap : 又执行map的操作由执行flatten的操作
- 有一个先后顺序 先map再flatten 那你肯定会奇怪那为什么会叫flatMap呢 而不是mapFlat呢 其实这只是为了好读
val nums = listOf(listOf(1,2), listOf(3,4), listOf(5,6)) fun main() { nums.flatMap { listOf(it,9) }.run(::println) nums.map { listOf(it,9) }.run(::println) } //输出: [[1, 2], 9, [3, 4], 9, [5, 6], 9] [[[1, 2], 9], [[3, 4], 9], [[5, 6], 9]]
-
将 listOf(it,9)里面全部的元素都添加到一个顶级的集合里面去 由于这个集合里面存放这 一个子集合和一个数字元素 所以在输出中就是一个集合一个数字
-
看看源码
public inline fun <T, R> Iterable<T>.flatMap(transform: (T) -> Iterable<R>): List<R> { return flatMapTo(ArrayList<R>()/*传入的空集合*/, transform) } public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.flatMapTo(destination: C, transform: (T) -> Iterable<R>): C { for (element in this) { val list = transform(element) //表换成一个新的集合 destination.addAll(list)//将新集合里面打的元素都加入进去 } return destination//返回那个新集合 }
- 可以看到确实是先变换将变换后的集合里的全部元素都添加到一个新的空集合里面去
-
几点建议
如果lambda表达式是一个一对一的函数的 就是说 接收一个值返回一个值 那么就可以用map
如果lambda表达式是一个一对多的函数 就是说 接收一个值返回一个集合的话 如果你就是想返回集合的集合那么就可以使用map如果是想返回一个顶级集合的话就用flatMap()
-
除此之外 可以 利用函数管道转换数据的思想进行数据的排序和分组
- sortedBy() :
val nums = listOf(1,2, 3,4,5,6) fun main() { nums.sortedBy { it }.run(::println) } //输出 [1, 2, 3, 4, 5, 6]
- 可以看到我们排序时的排序条件就是我们lambda表达式的返回值 默认是升序
看看源码
public inline fun <T, R : Comparable<R>> Iterable<T>.sortedBy(crossinline selector: (T) -> R?): List<T> { return sortedWith(compareBy(selector)) } public fun <T> Iterable<T>.sortedWith(comparator: Comparator<in T>): List<T> { if (this is Collection) { if (size <= 1) return this.toList() @Suppress("UNCHECKED_CAST") return (toTypedArray<Any?>() as Array<T>).apply { sortWith(comparator) }.asList() } return toMutableList().apply { sortWith(comparator) }
-
我看不懂 ^_^ 大佬来教教我 — 以后内力大增再来看看
-
降序的话就可以用 : sortedByDesending()
val nums = listOf(1,2, 3,4,5,6) fun main() { nums.sortedByDescending{ it }.run(::println) } //输出 : [6, 5, 4, 3, 2, 1]
-
groupBy() : 分组 : 底层是一个hashMap以我们传入的lambda的结果作为键 匹配到的值作为值存入到一个集合里面
val nums = listOf(1,2, 3,4,5,6)
fun main() {
nums.groupBy { it > 4 }.run(::println)
}
//输出 : {false=[1, 2, 3, 4], true=[5, 6]}
- 可以看到以我们lambda表达式的结果作为分组条件 以及作为map的键 值集合里面的元素默认就是原集合里面的元素
- 但是是可以指定放入什么元素的
val nums = listOf(1,2, 3,4,5,6)
fun main() {
nums.groupBy ({ it > 4 }){"~$it~"}.run(::println)
}
//输出 : {false=[~1~, ~2~, ~3~, ~4~], true=[~5~, ~6~]}
-
第一个lambda表达式放分组条件 第二个lambda表达式放存放在值集合里面的元素 这两个lambda的实参还是原集合的元素
-
源码以后来追追
-
序列
-
因为利用内部迭代器在数据量很大的时候有可能会导致性能的问题 所以为了解决这个问题就引入了序列来解决这个问题即可以保持优雅又可以减少性能损失
val nums = listOf(1,2, 3,4,5,6)
fun main() {
nums.map {
val new = it*2
println("非序列 $new")
new
}.first().run(::println)
}
//输出:
非序列 2
非序列 4
非序列 6
非序列 8
非序列 10
非序列 12
2
- 序列之后
val nums = listOf(1,2, 3,4,5,6)
fun main() {
nums.asSequence().map {
val new = it*2
println("序列 $new")
new
}.first().run(::println)
}
//输出 :
序列 2
2
-
可以看见如果数据很多的话那么肯定是序列胜出
-
这就是集合和序列的区别
- 集合是急切的 就是必须的把自己的任务执行完才会把执行完的结果交给下一个执行的函数
- 序列是懒的 就是先做一些让其他需要我结果的函数有用的就可以了 当最后一个执行函数(终端函数结束后)我就不干了 前面代码的输出也验证了这两点
- 通过序列的这个特性就可以规避一些用不到的计算了
-
简单的理解就是 集合是横向执行 只有完成了某个任务后才会接着完成后面的任务 而 序列就是先完成一部分任务 如果这一部分任务可以应付期待的结果了那么我就没必要做哪些还没做完的任务
-
但是序列不是凭空产生的 是建立在集合之上的 就是对集合的包装
-
无限序列成为可能
-
大致的思想就是 : 有一个可以延续进行的lambda表达式,但是只有在调用他的时候他才开始计算 不会一开始就计算 所以这就使得无限序列成为可能
val nums = listOf(1,2, 3,4,5,6)
fun main() {
val primes = generateSequence(5,::nextPrime)
primes.take(6).toList().run(::println)
}
fun isPrime(n:Long) = n>1 && (2 until n).none{i-> n%i== 0L}//判断是不是质数
tailrec/*防止堆栈溢出*/ fun nextPrime(n : Long):Long
= if (isPrime(n+1)) n+1 else nextPrime(n+1) //求下一个质数
-
在**val primes = generateSequence(5,::nextPrime)**这个的时候并不会马上计算全部的质数而是在
primes.take(6).toList() 先take确定无限序列的区域让后调用toList() 的时候才执行nextPrime()方法这就是懒必须等到迫不得已的时候才会去执行
-
另一种创建序列的方法
fun main() {
val primes = sequence {
var i : Long = 0
while(true){
i++
if (isPrime(i)){
yield(i)/*这里是重点*/
}
}
}
primes.take(6).toList().run(::println)
}
fun isPrime(n:Long) = n>1 && (2 until n).none{i-> n%i== 0L}
-
这里的yield简单理解一下就是 : 向调用方返回一个值然后执行下一行代码
-
延续给人一种编写了具有多个返回点的函数的错觉 – 协程那一节会详细谈谈
-
kotlin杂谈系列七结束