前言

在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都已经开源。有兴趣可以看这里。项目会持续更新,大家可以关注一下。

以上就是Kotlin的Collection与Sequence操作异同点详解的详细内容,更多关于Kotlin Collection Sequence异同的资料请关注Devmax其它相关文章!

Kotlin的Collection与Sequence操作异同点详解的更多相关文章

  1. Kotlin难点解析:extension和this指针

    扩展是Kotlin语言中使用非常简单的一个特性。关于这个问题,其实我之前的一篇文章[[Kotlin]LambdaandExtension](https://www.jianshu.com/p/d7a...中有提到过。为了解决这个问题,官方提出了两个新的概念:dispatchreceiver和extensionreceiver。extensionreceiver:中文翻译为扩展接收者。为了简化,这里我们将dispatchreceiver简称为DR,将extensionreceiver简称为ER。如果你习惯了

  2. android – Kotlin类NoClassDefFoundError崩溃

    我有一个使用以下库的现有Android项目:>Autovalue>Dagger2>RxJava>Retrolambda我正在尝试添加Kotlin支持,以便我可以将项目慢慢迁移到Kotlin.这就是我所做的.>添加了Kotlin依赖.>将其中一个类转换为Kt类并转移到src/main/kotlin/..包中.>在源集中添加了kotlin.sourceSets{main.java.srcDirs=’s

  3. android – Kotlin和Dagger2

    我正在尝试将Kotlin添加到我的项目中,但在启用Kotlin之后我无法构建,因为Dagger2类不再生成.我尝试了第二个项目,我有同样的问题.这些是我为启用Kotlin所做的改变:项目build.gradle:Appbuild.gradle:错误发生在这里:其中不再定义DaggerObjectGraph.任何帮助将不胜感激.解决方法只需删除

  4. android – 在Kotlin中不能使用argb color int值吗?

    当我想在Kotlin中为TextView的textColor设置动画时:发生此错误:似乎在Kotlin中不能将值0xFF8363FF和0xFFC953BE强制转换为Int,但是,它在Java中是正常的:有任何想法吗?提前致谢.解决方法0xFF8363FF是Long,而不是Int.你必须明确地将它们转换为Int:关键是0xFFC953BE的数值是4291384254,因此它应该存储在Long变量中.但这里的高位是符号位,表示负数:-3583042,可以存储在Int中.这就是两种语言之间的区别.在Kotlin

  5. 什么是我可以使用Kotlin的最早的Android API级别?

    我认为这个问题很清楚但是我能在Kotlin上定位的最早API级别是什么?解决方法实际上,任何API级别.这是因为Kotlin被编译为JVM6平台的字节码,所有AndroidAPI级别都支持该字节码.因此,除非您在Kotlin代码中使用任何较新的AndroidAPI,否则它不需要任何特定的API级别.

  6. android – Kotlin数据类和可空类型

    我是Kotlin的新手,我不知道为什么编译器会抱怨这段代码:编译器抱怨测试?.data.length,它说我应该这样做:test?.length.但是数据变量是String,而不是String?,所以我不明白为什么我要把它?当我想检查长度.解决方法表达式test?.data部分可以为空:它是test.data或null.因此,获取其长度并不是零安全的,而是应该再次使用safecalloperator:test?.length.可空性通过整个调用链传播:你必须将这些链写成?.)).e),因为,如果其中一个左

  7. android – Kotlin自定义获取执行方法调用

    像这样的东西:仍在使用Kotlin并且不确定get()方法是否会引用编辑器而不是创建新的编辑器.解决方法第二个属性声明适合您的需要:它有一个customgetter,因此获取属性值将始终执行getter,并且不存储该值.你可能会被等号get()=…

  8. android – Kotlin合成扩展和几个包含相同的布局

    我找了一些这样的:我在Studio中看到我可以访问dayName但是dayNameTextView引用了哪一个?正常,如果我只有一个包含的布局,它工作正常.但现在我有多次包含相同的布局.我当然可以这样做:但我正在寻找好的解决方案.版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

  9. android – java.lang.IllegalArgumentException:指定为非null的参数为null:方法kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull

    我收到了这个错误java.lang.IllegalArgumentException:指定为非null的参数为null:方法kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull,参数事件为线覆盖funonEditorAction(v:TextView,actionId:Int,event:KeyEvent)以下是整个代码.这段代码最初是在ja

  10. android – Kotlin:如何访问CustomView的Attrs

    我在Kotlin中创建了一个自定义视图,并希望访问它的属性资源.以下是我的代码请注意,这将在init函数的attrs中出错.我想知道如何进入attrs?

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部