Kotlin的Collection与Sequence操作?他们之间有什么异同点?
myzbx 2025-06-10 00:03 23 浏览
前言
在Android开发中,集合是我们必备的容器,Kotlin的标准库中提供了很多处理集合的方法,而且还提供了两种基于容器的工作方式:Collection 和 Sequence。
Collection 是我们常见的,而 Sequence 确是我们少见的,甚至很多人都没有听说过(是的,说的就是我 )
本篇文章就来介绍一下常用的一些集合操作符,以及 Collection 与 Sequence 的区别,到底怎么用!
Collection 的常见操作
Collection 集合,Kotlin的集合类型和Java不一样,Kotlin的集合分为可变(读写)和不可变(只读)类型(lists, sets, maps, etc),可变类型是在不可变类型前面加Mutable,以我们常用的三种集合类型为例:
List<out E> - MutableList<E>
Set<out E> - MutableSet<E>
Map<K, out V> - MutableMap<K, V>
其实他们的区别就是List实现了Collection接口,而MutableList实现的是List和MutableCollection接口。而 MutableCollection 接口实现了Collection 接口,并且在里面添加了add和remove等操作方法。
可变不可变只是为了区分只读和读写的操作,他们的操作符方式都是相同的。
集合的操作符说起来可就太多了
累计
//对所有元素求和
list.sum()
//将集合中的每一个元素代入lambda表达式,然后对lambda表达式的返回值求和
list.sumBy {
it % 2
}
//在一个初始值的基础上,从第一项到最后一项通过一个函数累计所有的元素
list.fold(100) { accumulator, element ->
accumulator + element / 2
}
//同fold,只是迭代的方向相反
list.foldRight(100) { accumulator, element ->
accumulator + element / 2
}
//同fold,只是accumulator的初始值就是集合的第一个元素,element从第二个元素开始
list.reduce { accumulator, element ->
accumulator + element / 2
}
//同reduce但方向相反:accumulator的初始值就是集合的最后一个元素,element从倒数第二个元素开始往前迭代
list.reduceRight { accumulator, element ->
accumulator + element / 2
}
val list = listOf(1, 2, 3, 4, 5, 6)
//只要集合中的任何一个元素满足条件(使得lambda表达式返回true),any函数就返回true
list.any {
it >= 0
}
//集合中的全部元素都满足条件(使得lambda表达式返回true),all函数才返回true
list.all {
it >= 0
}
//若集合中没有元素满足条件(使lambda表达式返回true),则none函数返回true
list.none {
it < 0
}
//count函数的返回值为:集合中满足条件的元素的总数
list.count {
it >= 0
}
遍历
//遍历所有元素
list.forEach {
print(it)
}
//同forEach,只是可以同时拿到元素的索引
list.forEachIndexed { index, value ->
println("position $index contains a $value")
}
showFields.forEach { (key, value) ->
最大最小
//返回集合中最大的元素,集合为空(empty)则返回null
list.max()
//返回集合中使得lambda表达式返回值最大的元素,集合为空(empty)则返回null
list.maxBy { it }
//返回集合中最小的元素,集合为空(empty)则返回null
list.min()
//返回集合中使得lambda表达式返回值最小的元素,集合为空(empty)则返回null
list.minBy { it }
过滤(去除)
//返回一个新List,去除集合的前n个元素
list.drop(2)
//返回一个新List,去除集合的后n个元素
list.dropLast(2)
//返回一个新List,去除集合中满足条件(lambda返回true)的第一个元素
list.dropWhile {
it > 3
}
//返回一个新List,去除集合中满足条件(lambda返回true)的最后一个元素
list.dropLastWhile {
it > 3
}
//返回一个新List,包含前面的n个元素
list.take(2)
//返回一个新List,包含最后的n个元素
list.takeLast(2)
//返回一个新List,仅保留集合中满足条件(lambda返回true)的第一个元素
list.takeWhile {
it>3
}
//返回一个新List,仅保留集合中满足条件(lambda返回true)的最后一个元素
list.takeLastWhile {
it>3
}
//返回一个新List,仅保留集合中满足条件(lambda返回true)的元素,其他的都去掉
list.filter {
it > 3
}
//返回一个新List,仅保留集合中不满足条件的元素,其他的都去掉
list.filterNot {
it > 3
}
//返回一个新List,仅保留集合中的非空元素
list.filterNotNull()
//返回一个新List,仅保留指定索引处的元素
list.slice(listOf(0, 1, 2))
映射
//将集合中的每一个元素代入lambda表达式,lambda表达式必须返回一个元素
//map的返回值是所有lambda表达式的返回值所组成的新List
//例如下面的代码和listOf(2,4,6,8,10,12)将产生相同的List
list.map {
it * 2
}
//将集合中的每一个元素代入lambda表达式,lambda表达式必须返回一个集合
//而flatMap的返回值是所有lambda表达式返回的集合中的元素所组成的新List
//例如下面的代码和listOf(1,2,2,3,3,4,4,5,5,6,6,7)将产生相同的List
list.flatMap {
listOf(it, it + 1)
}
//和map一样,只是lambda表达式的参数多了一个index
list.mapIndexed { index, it ->
index * it
}
//和map一样,只不过只有lambda表达式的非空返回值才会被包含在新List中
list.mapNotNull {
it * 2
}
//根据lambda表达式对集合元素进行分组,返回一个Map
//lambda表达式的返回值就是map中元素的key
//例如下面的代码和mapOf("even" to listOf(2,4,6),"odd" to listOf(1,3,5))将产生相同的map
list.groupBy {
if (it % 2 == 0) "even" else "odd"
}
元素
list.contains(2)
list.elementAt(0)
//返回指定索引处的元素,若索引越界,则返回null
list.elementAtOrNull(10)
//返回指定索引处的元素,若索引越界,则返回lambda表达式的返回值
list.elementAtOrElse(10) { index ->
index * 2
}
//返回list的第一个元素
list.first()
//返回list中满足条件的第一个元素
list.first {
it > 1
}
//返回list的第一个元素,list为empty则返回null
list.firstOrNull()
//返回list中满足条件的第一个元素,没有满足条件的则返回null
list.firstOrNull {
it > 1
}
list.last()
list.last { it > 1 }
list.lastOrNull()
list.lastOrNull { it > 1 }
//返回元素2第一次出现在list中的索引,若不存在则返回-1
list.indexOf(2)
//返回元素2最后一次出现在list中的索引,若不存在则返回-1
list.lastIndexOf(2)
//返回满足条件的第一个元素的索引
list.indexOfFirst {
it > 2
}
//返回满足条件的最后一个元素的索引
list.indexOfLast {
it > 2
}
//返回满足条件的唯一元素,如果没有满足条件的元素或满足条件的元素多于一个,则抛出异常
list.single {
it == 5
}
//返回满足条件的唯一元素,如果没有满足条件的元素或满足条件的元素多于一个,则返回null
list.singleOrNull {
it == 5
}
排序&逆序
val list = listOf(1, 2, 3, 4, 5, 6)
//返回一个颠倒元素顺序的新集合
list.reversed()
/**
* 返回一个升序排序后的新集合
*/
list.sorted()
//将每个元素代入lambda表达式,根据lambda表达式返回值的大小来对集合进行排序
list.sortedBy {
it*2
}
/**
* 功能和上面一样 -> 上面是从小到大排列,这个返回的是从大到小
*/
list.sortedDescending()
list.sortedByDescending {
it*2
}
/**
* 根据多个条件排序
* 先根据age 升序排列,若age相同,根据name升序排列,但是都是默认的升序排列
*/
personList.sortWith(compareBy({ it.age }, { it.name }))
/**
* 根据多个条件排序,自定义的规则
* 构造一个Comparator对象,完成排序逻辑:先按age降序排列,若age相同,则按name升序排列
*/
val c1: Comparator<Person> = Comparator { o1, o2 ->
if (o2.age == o1.age) {
o1.name.compareTo(o2.name)
} else {
o2.age - o1.age
}
}
personList.sortWith(c1)
//上面的自定义方式可以通过JavaBean实现Comparable 接口实现自定义的排序
data class Person(var name: String, var age: Int) : Comparable<Person> {
override fun compareTo(other: Person): Int {
if (this.age == other.age) {
return this.name.compareTo(other.name)
} else {
return other.age - this.age
}
}
}
//sorted 方法返回排序好的list(已有有排序规则的用sorted,不要用sortedby了)
val sorted = personList.sorted()
Sequence 的常见操作
Sequence 是 Kotlin 中一个新的概念,用来表示一个延迟计算的集合。Sequence 只存储操作过程,并不处理任何元素,直到遇到终端操作符才开始处理元素,我们也可以通过 asSequence 扩展函数,将现有的集合转换为 Sequence ,代码如下所示
val list = mutableListOf<Person>()
for (i in 1..10000) {
list.add(Person("name$i", (0..100).random()))
}
list.asSequence()
当我们拿到结果之后我们还能通过toList再转换为集合。
list.asSequence().toList()
Sequence的操作符绝大部分都是和 Collection 类似的。常用的一些操作符是可以直接平替使用的。
val list2 = list.asSequence()
.filter {
it.age > 50
}.map {
it.name
}.take(3).toList()
居然他们的操作符都长得一样,效果也都一样,导致 Sequence 与 Collection 就很类似,那么既生瑜何生亮!为什么需要这么个东西?既然 Collection 能实现效果为什么还需要 Sequence 呢?他们的区别又是什么呢?
区别与对比
Collection 是立即执行的,每一次中间操作都会立即执行,并且把执行的结果存储到一个容器中,没多一个中间操作符就多一个容器存储结果。
public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
比如常用的 map 和 filter 都是会新建一个 ArrayList 去存储结果,
Sequence 是延迟执行的,它有两种类型,中间操作和末端操作 ,主要的区别是中间操作不会立即执行,它们只是被存储起来,中间操作符会返回另一个Sequence,仅当末端操作被调用时,才会按照顺序在每个元素上执行中间操作,然后执行末端操作。
public fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R> {
return TransformingSequence(this, transform)
}
public fun <T> Sequence<T>.filter(predicate: (T) -> Boolean): Sequence<T> {
return FilteringSequence(this, true, predicate)
}
比如常用的 map 和 filter 都是直接返回 Sequence 的this 对象。
public inline fun <T> Sequence<T>.first(predicate: (T) -> Boolean): T {
for (element in this) if (predicate(element)) return element
throw NoSuchElementException("Sequence contains no element matching the predicate.")
}
然后在末端操作中,会对 Sequence 中的元素进行遍历,直到预置条件匹配为止。
这里我们举一个示例来演示一下:
我们使用同样的筛选与转换,来看看效果
val list = mutableListOf<Person>()
for (i in 1..10000) {
list.add(Person("name$i", (0..100).random()))
}
val time = measureTimeMillis {
val list1 = list.filter {
it.age > 50
}.map {
it.name
}.take(3)
YYLogUtils.w("list1$list1")
}
YYLogUtils.w("耗费的时间$time")
val time2 = measureTimeMillis {
val list2 = list.asSequence()
.filter {
it.age > 50
}.map {
it.name
}.take(3).toList()
YYLogUtils.w("list2$list2")
}
YYLogUtils.w("耗费的时间2$time2")
运行结果:
当集合数量为10000的时候,执行时间能优秀百分之50左右:
当集合数量为5000的时候,执行时间相差比较接近:
当集合数量为3000的时候,此时的结果就反过来了,Sequence延时执行的优化效果就不如List转换Sequence再转换List了:
总结
Collection 会立即执行对数据的操作,而 Sequence 则是延迟执行,如果数据量比较小,可以使用 Collection ,如果处理的数据量比较大,Sequence 是最好的选择,因为不会创建中间结果集,内存开销更小。
根据上面的测试发现,也不能无脑的使用 Sequence 来优化筛选转换效果,当数据量没有达到一定程度的时候,可能会出现负优化的效果。
当然我们的对象是很简单的对象,我们的筛选也是简单筛选,当对象复杂度到一定程度,筛选条件复杂到一定程度,集合的数量到一定的程度,我们才可以考虑 使用 Sequence 来达到优化效果。
Sequence 的具体的使用还是需要具体场景具体分析来使用,就我们Android应用开发的场景来说,很少会出现这么大的数据量,所以一般来说我们平常开发使用 Collection 即可。
好了,本文的全部代码与Demo都已经开源。有兴趣可以看这里。项目会持续更新,大家可以关注一下。
如果感觉本文对你有一点点的启发,还望你能点赞支持一下,你的支持是我最大的动力。
作者:newki
链接:
https://juejin.cn/post/7155765126627344392
来源:稀土掘金
相关推荐
- 如何设计一个优秀的电子商务产品详情页
-
加入人人都是产品经理【起点学院】产品经理实战训练营,BAT产品总监手把手带你学产品电子商务网站的产品详情页面无疑是设计师和开发人员关注的最重要的网页之一。产品详情页面是客户作出“加入购物车”决定的页面...
- 怎么在JS中使用Ajax进行异步请求?
-
大家好,今天我来分享一项JavaScript的实战技巧,即如何在JS中使用Ajax进行异步请求,让你的网页速度瞬间提升。Ajax是一种在不刷新整个网页的情况下与服务器进行数据交互的技术,可以实现异步加...
- 中小企业如何组建,管理团队_中小企业应当如何开展组织结构设计变革
-
前言写了太多关于产品的东西觉得应该换换口味.从码农到架构师,从前端到平面再到UI、UE,最后走向了产品这条不归路,其实以前一直再给你们讲.产品经理跟项目经理区别没有特别大,两个岗位之间有很...
- 前端监控 SDK 开发分享_前端监控系统 开源
-
一、前言随着前端的发展和被重视,慢慢的行业内对于前端监控系统的重视程度也在增加。这里不对为什么需要监控再做解释。那我们先直接说说需求。对于中小型公司来说,可以直接使用三方的监控,比如自己搭建一套免费的...
- Ajax 会被 fetch 取代吗?Axios 怎么办?
-
大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!今天给大家带来的主题是ajax、fetch...
- 前端面试题《AJAX》_前端面试ajax考点汇总
-
1.什么是ajax?ajax作用是什么?AJAX=异步JavaScript和XML。AJAX是一种用于创建快速动态网页的技术。通过在后台与服务器进行少量数据交换,AJAX可以使网页实...
- Ajax 详细介绍_ajax
-
1、ajax是什么?asynchronousjavascriptandxml:异步的javascript和xml。ajax是用来改善用户体验的一种技术,其本质是利用浏览器内置的一个特殊的...
- 6款可替代dreamweaver的工具_替代powerdesigner的工具
-
dreamweaver对一个web前端工作者来说,再熟悉不过了,像我07年接触web前端开发就是用的dreamweaver,一直用到现在,身边的朋友有跟我推荐过各种更好用的可替代dreamweaver...
- 我敢保证,全网没有再比这更详细的Java知识点总结了,送你啊
-
接下来你看到的将是全网最详细的Java知识点总结,全文分为三大部分:Java基础、Java框架、Java+云数据小编将为大家仔细讲解每大部分里面的详细知识点,别眨眼,从小白到大佬、零基础到精通,你绝...
- 福斯《死侍》发布新剧照 "小贱贱"韦德被改造前造型曝光
-
时光网讯福斯出品的科幻片《死侍》今天发布新剧照,其中一张是较为罕见的死侍在被改造之前的剧照,其余两张剧照都是死侍在执行任务中的状态。据外媒推测,片方此时发布剧照,预计是为了给不久之后影片发布首款正式预...
- 2021年超详细的java学习路线总结—纯干货分享
-
本文整理了java开发的学习路线和相关的学习资源,非常适合零基础入门java的同学,希望大家在学习的时候,能够节省时间。纯干货,良心推荐!第一阶段:Java基础重点知识点:数据类型、核心语法、面向对象...
- 不用海淘,真黑五来到你身边:亚马逊15件热卖爆款推荐!
-
Fujifilm富士instaxMini8小黄人拍立得相机(黄色/蓝色)扫二维码进入购物页面黑五是入手一个轻巧可爱的拍立得相机的好时机,此款是mini8的小黄人特别版,除了颜色涂装成小黄人...
- 2025 年 Python 爬虫四大前沿技术:从异步到 AI
-
作为互联网大厂的后端Python爬虫开发,你是否也曾遇到过这些痛点:面对海量目标URL,单线程爬虫爬取一周还没完成任务;动态渲染的SPA页面,requests库返回的全是空白代码;好不容易...
- 最贱超级英雄《死侍》来了!_死侍超燃
-
死侍Deadpool(2016)导演:蒂姆·米勒编剧:略特·里斯/保罗·沃尼克主演:瑞恩·雷诺兹/莫蕾娜·巴卡林/吉娜·卡拉诺/艾德·斯克林/T·J·米勒类型:动作/...
- 停止javascript的ajax请求,取消axios请求,取消reactfetch请求
-
一、Ajax原生里可以通过XMLHttpRequest对象上的abort方法来中断ajax。注意abort方法不能阻止向服务器发送请求,只能停止当前ajax请求。停止javascript的ajax请求...
- 一周热门
- 最近发表
- 标签列表
-
- HTML 简介 (30)
- HTML 响应式设计 (31)
- HTML URL 编码 (32)
- HTML Web 服务器 (31)
- HTML 表单属性 (32)
- HTML 音频 (31)
- HTML5 支持 (33)
- HTML API (36)
- HTML 总结 (32)
- HTML 全局属性 (32)
- HTML 事件 (31)
- HTML 画布 (32)
- HTTP 方法 (30)
- 键盘快捷键 (30)
- CSS 语法 (35)
- CSS 轮廓宽度 (31)
- CSS 谷歌字体 (33)
- CSS 链接 (31)
- CSS 定位 (31)
- CSS 图片库 (32)
- CSS 图像精灵 (31)
- SVG 文本 (32)
- 时钟启动 (33)
- HTML 游戏 (34)
- JS Loop For (32)
