Kotlin的集合是让我为之心动的地方,丰富的高阶函数帮助咱们高效开发。今天介绍Kotlin的基础集合用法、获取集合元素的函数、过滤元素的函数、元素排序的函数、元素统计的函数、集合元素映射的函数、集合的交差并补集的函数。还有一些工做中的经验。html
需求:前端有一个二维表格,但愿后端提供一个支持批量更新、建立、删除功能的接口。且对部分字段的值有特殊要求。前端
分析:这样的需求并很多见,如工厂车间的能耗统计。统计的是每一个车间,每台设备的能耗值。这些值是能够被用户手动维护的。且这些值都是有取值范围。java
1)、特殊字段拦截:若是是一条数据的操做,能够经过注解对字段进行校验。可是批量操做,要考虑事务回滚带来的不必的开销。能够考虑用代码进行特殊字段的过滤。程序员
2)、区分建立、更新和删除:一个接口完成三个操做,必需要清楚哪些数据。咱们能够经过是否有id来区分更新和建立。经过旧数据和新数据求差集区分删除。sql
下面是一段伪代码,为了方便演示集合的函数,一些方法都放在一块儿介绍。数据库
@Transactional fun modifyEquipmentEnergyValue(equipmentEnergyValues: List<EquipmentEnergyValue>): OperateStatus { // 经过上下文获取当前登陆的用户,从而获取其权限 val currentUser = ContextUtils.getCurrentUser() // 一个用户关联多个角色,一个角色绑定多个权限,全部必定会有重复的权限存在 // 经过flatMap 方法获取全部权限,在经过.toSet()方法去重 val authorities = currentUser.roles?.flatMap { it.authorities.orEmpty() }.toSet() // 先判断是否针对全部设备都有权限,避免不必的事务回滚 // 经过find 方法找出没有权限设备 equipmentEnergyValues.find { !authorities.contains(it.equipment.id) }?.let { throw AuthenticationException("您没有权限$it设备能耗,请联系工程人员") } // 先判断是否存在重复数据或者不合理数据,避免不必的事务回滚 // 设备名称不能重复,用map映射出一个新集合,原集合不受影响 val equipmenNameSize = equipmentEnergyValues.map { it.equipment.name }.toSet().size if (equipmenNameSize != equipmentEnergyValues.size) { throw IllegalArgumentException("设备不能重复修改") } // 经过 maxBy 方法找出值最大的一项 if (equipmentEnergyValues.maxBy { it.value }.value >= 1000) { throw IllegalArgumentException("设备能耗值不符合规范") } // 旧数据和新数据求差集,找出须要清空的数据(或者设为零) val oldEquipmentEnergyValues = equipmentRepository.findByLocationAndDate(xxx,xxx) oldEquipmentEnergyValues.subtract(equipmentEnergyValues).forEach { // 删除 } // 更新数据时考虑null值覆盖的问题 equipmentEnergyValues.forEach { // 经过id判断是更新仍是建立,用BeanUtils.copyProperties作复制时须要注意null的问题 } return OperateStatus() }
既然写了接口逻辑,顺势谈谈我对接口的肤浅理解(听了许多小白后端和前端的矛盾)。后端
1)、首先接口是能够独立完成业务逻辑。调用者并不须要关系业务逻辑,只需按照给定的参数发送请求,就能够获取想要的结果。api
2)、其次接口是有较强的健壮能力。后端的业务逻辑不能由于调用者的错误请求,而报出500的错误,至少也是已知的业务错误。数组
3)、最后接口应该尽可能避免级联删数据功能。全部的删除操做尽量甩锅给用户。jvm
随着前端功能愈来愈强大,先后端在处理接口的问题上矛盾也是愈来愈多。一些后端处理的逻辑都开始交给前端(部分前端开始膨胀了,部分后端开始偷懒了)。这致使一些分工的不明确,甚至一些本该由后端处理的逻辑也交给了前端。彷佛在他们眼里,后端就是数据的crud。好在这样的后端大多都比较年轻,也许后期会成长起来。只能心疼一下前端。
对于我而言,不会把主动权交给前端。提供一个健壮、优质的接口是对本身的要求。给小白一些建议:对接口负责,就是对本身负责,也是对其余同事负责。
和Java集合不一样的是,Kotlin的集合分可变和不可变两种集合。同时也支持两种集合相互切换。
// 声明并初始化不可变List集合 val list: List<Any> = listOf<Any>(1, "2", 3) // 声明并初始化可变MutableList集合 val mutableList: MutableList<Any> = mutableListOf<Any>(4, "5", 6) mutableList.add("7") list.map { print("$it \t") } mutableList.map { print("$it \t") }
// 声明并初始化不可变Set集合 val set: Set<Any> = setOf<Any>(1, "2", 3, "3") // 声明并初始化可变MutableSet集合 val mutableSet: MutableSet<Any> = mutableSetOf<Any>(4, "5", 6) mutableSet.add(6) set.map { print("$it \t") } mutableSet.map { print("$it \t") }
// 声明并初始化不可变Map集合 val map: Map<String, Any> = mapOf("k1" to "v1" , "k2" to 3) // 声明并初始化可变MutableMap集合 val mutableMap: MutableMap<String, Any> = mutableMapOf("k1" to "v1" , "k1" to 3) map.map { println("key : ${it.key} \t value : ${it.value}") } mutableMap.map { println("key : ${it.key} \t value : ${it.value}") }
用Java语言开发时,咱们一般用循环遍历集合的每一个元素。有时候也会经过下标直接获取指定元素。此时原则上时须要咱们先考虑集合元素的长度,以免下标越界的异常问题。但每每咱们会抱着侥幸的心态直接经过get(index)
方法获取元素。通常状况下咱们会在黑盒自测中发现越界问题(有部分朋友从不黑盒,直接白盒测试,并反问:测试的工做难道不就是发现问题?)。即使是在运行中出现越界问题,也能够甩锅给数据库。但无论怎么样,由于越界致使系统不稳定是不合理的。
用Kotlin语言开发时,咱们会发现有不少带有"Or"字样的方法。好比我经常使用的getOrElse
,firstOrNull
等方法。分别表示:经过下标若是没有获取到值,则返回自定的值。和获取集合的第一个元素,若集合为空则返回null。正由于Kotlin提供了不少相似getOrElse
,firstOrNull
的方法。很大程度上提升了咱们的开发效率,和减小了一些低级错误发生的几率。接下来咱们学习一下Kotlin具体有哪些获取集合元素的方法(single方法没怎么用过)
get(index)
: List的函数,经过下标获取指定元素。若找不到值(下标越界),会抛出IndexOutOfBoundsException
异常getOrElse(index, {...})
: List的扩展函数,经过下标获取指定元素。找不到值则返回默认值getOrNull(index)
: List的扩展函数,经过下标获取指定元素。找不到值则返回nullelementAtOrElse(index, {...})
: Iterable接口的扩展函数,功能同getOrElse
方法elementAtOrNull(index)
: Iterable接口的扩展函数,功能同getOrNull
方法first()
: 获取集合第一个元素。若没有返回值,则抛出NoSuchElementException
异常first{}
: 获取集合中指定元素的第一个元素。若没有返回值,则抛出NoSuchElementException
异常firstOrNull()
: 获取集合第一个元素。若没有返回值,返回nullfirstOrNull{}
: 获取集合指定元素的第一个元素。若没有返回值,返回nulllast()
: 与first()
相反last{}
: 与first{}
相反lastOrNull{}
: 与firstOrNull()
相反lastOrNull()
: 与firstOrNull{}
相反indexOfFirst{...}
: 返回集合中第一个知足条件元素的下标indexOfLast{...}
: 返回集合中最后一个知足条件元素的下标single()
: Returns the single element, or throws an exception if the collection is empty or has more than one element. 官方api文档地址 single{}
: 按照条件返回单个元素,若集合为空或者有多个元素知足条件,则报错singleOrNull()
: 返回单个元素,若集合为空或者有多个元素,则返回nullsingleOrNull{}
: 按照条件返回单个元素,若集合为空或者有多个元素知足条件,则返回null在使用获取元素的方法时,推荐方法名中带有"Or"字样的方法,能够减小不少没必要要的报错。
List集合经过下标获取元素能够用get,getOrElse,getOrNull函数,但其余集合没有这些方法。
笔者单方面认为single函数和数据库的惟一约束的功能有点相似,在使用Kotlin的过程当中,你会发现它有不少和数据库相似的功能。
val list: MutableList<Int> = mutableListOf(1,2,3,4,5) println("getOrElse : ${list.getOrElse(10,{ 20 })}") println("getOrNull : ${list.getOrNull(10)}") println("firstOrNull : ${list.firstOrNull()}") println("firstOrNull : ${list.firstOrNull { it > 3 }}") println("indexOfFirst : ${list.indexOfFirst { it > 3 }}") println("indexOfLast : ${list.indexOfLast { it > 3 }}") ----------------------------------------------------- getOrElse : 20 getOrNull : null firstOrNull : 1 firstOrNull : 4 indexOfFirst : 3 indexOfLast : 4
用Java语言开发时,给对象集合作排序是常有的业务逻辑。(Java8以后的写法不太了解)按照我以前工做中排序的代码其实也并不复杂,十行代码基本能够搞定一个排序逻辑。注意是一个,一个。业务中存在大量的排序需求,这种代码会反复出现。对于我这种佛系程序员兼CV高手而言,早已经习觉得常了。但自从用了Kotlin的sortedBy
方法后。忽然以为Kotlin用起来倍儿爽!
用Java7开发了几年,Java8只接触了一点皮毛,如今Java12都已经出来了。常常看到一些文章为了突出某个语言的强大,而去踩其余语言。我只想问:who are you?每一个语言都有本身独特的一面.神仙打架,咱们负责吃瓜就好。就懂点皮毛的人,瞎掺和啥?
Collections.sort(list,new Comparator () { @Override public int compare(Object o1, Object o2) { return o1.compareTo(e2); } });
用Kotlin语言开发时,咱们不须要重复写相似上面的排序代码,Kotlin已经帮咱们封装好了,只须要咱们写须要排序的字段便可。其底层也是经过Java 的Collections.sort实现的。全部咱们就放心大胆的用吧。
public inline fun <T, R : Comparable<R>> MutableList<T>.sortBy(crossinline selector: (T) -> R?): Unit { if (size > 1) sortWith(compareBy(selector)) } @kotlin.jvm.JvmVersion public fun <T> MutableList<T>.sortWith(comparator: Comparator<in T>): Unit { if (size > 1) java.util.Collections.sort(this, comparator) }
sortedBy{}
: 根据条件给集合升序,经常使用与给对象集合的某个字段排序,并返回排序后的集合,原集合顺序不变reversed()
: 集合反序。与降序不一样,反序指的是和初始化的顺序相反sorted()
: 天然升序,经常使用于给普通集合排序sortedDescending()
: 天然降序sortedByDescending{}
: 根据条件给集合降序sortBy{}
: 根据条件给原集合升序,经常使用与给对象集合的某个字段排序sortByDescending{}
: 根据条件给原集合降序reverse()
: 原集合反序千万不要把反序理解成了倒序,前车可鉴
sortBy方法是对原集合作排序操做,而sortedBy方法是返回一个排序后的新集合,原集合排序没有变
kotlin排序方法中能够用and,or 组装多个条件,但效果并不理想
data class Person( var name: String = "", var age: Int = 0, var salary: Double = 0.0 ) val persons = mutableListOf(Person("n1", 20, 2000.0), Person("n2", 24, 4000.0), Person("n3", 28, 6000.0), Person("n4", 26, 8000.0), Person("n5", 34, 7000.0), Person("n6", 44, 5000.0)) persons.sortedBy { it.age }.map { println(it) } persons.map { it.age }.sorted() persons.sortBy { it.age } persons.reversed()
Java8也提供了Map和Filter函数用于转换和过滤对象,使开发变得更轻松,遥想当年在for循环里面加if语句。慢慢成了过去式。集合遍历以前先filter一下,已经成了我开发过程当中不可或缺的一步。虽然 filter
函数相对于Kotlin的 getOrNull
和 sortedBy
函数,并无给人一种眼前一亮的感受。但它提升了代码的可读性和美观性。
filter{...}
: 过滤不知足条件的元素,返回只知足条件元素列表,不影响原集合filterNot{...}
: 和filter{}
函数的功能相反filterNotNull()
: 过滤掉集合中为null的元素filterIndexed{...}
: 在filter{}
函数上多了一个下标功能,能够经过索引进一步过滤distinct()
: 去除重复元素,返回元素的顺序和原集合顺序一致distinctBy{...}
: 根据操做元素后的结果去去重,去除的是操做前的元素take(num)
: 返回集合中前num个元素组成的集合takeWhile{...}
: 从第一个元素开始遍历集合,当出现第一个不知足条件元素时退出循环。返回全部知足条件的元素集合takeLast(num)
: 和take
函数相反,返回集合中后num个元素组成的集合takeLastWhile{...}
: 从最后一个元素开始遍历集合,当出现第一个不知足条件元素时退出循环。返回全部知足条件的元素集合drop(num)
: 过滤集合中前num个元素dropWhile{...}
: 和执行takeWhile{...}
函数后获得的结果相反dropLast(num)
: 过滤集合中后num个元素dropLastWhile{...}
: 和执行takeLastWhile{...}
函数后获得的结果相反slice(...)
: 过滤掉全部不知足执行下标的元素。参数是下标集合或者是下标区间。以上Filter、Distinct、Take、Drop、Slice方法都返回一个处理后的新集合,不影响原集合。
Kotlin提供了丰富的函数供咱们使用,同时也吓退了不少朋友,别怕!Kotlin的函数都是买一送一的,学会一个,不愁另外一个。
val list = listOf(-3,-2,1,3,5,3,7,2,10,9) println("filter : ${list.filter { it > 1 }}") println("filterIndexed : ${list.filterIndexed { index, result -> index % 2 == 0 && result > 5 }}") println("take : ${list.take(5)}") println("takeWhile : ${list.takeWhile { it < 5 }}") println("drop : ${list.drop(5)}") println("distinct : ${list.distinct()}") println("distinctBy : ${list.distinctBy { it % 2 }}") println("slice : ${list.slice(IntRange(1,5))}") ----------------------------------------------------- filter : [3, 5, 3, 7, 2, 10, 9] filterIndexed : [7, 10] take : [-3, -2, 1, 3, 5] takeWhile : [-3, -2, 1, 3] drop : [3, 7, 2, 10, 9] distinct : [-3, -2, 1, 3, 5, 7, 2, 10, 9] distinctBy : [-3, -2, 1] slice : [-2, 1, 3, 5, 3]
在用Java8和Kotlin以前。和排序同样,在实现求最大值、平均值、求和等操做时,都要写不少冗余的代码。如今好了,Kotlin已经封装了这些方法。朋友们,千万不要过于依赖这些方法。有些一条sql能解决的问题,就不要把统计的逻辑留给代码完成。这里的方法更适合在业务处理过程当中,对一些简单集合的统计处理。若是是统计报表的功能,就不要有什么歪心思了。分享一篇关于统计的文章:常见的统计解决方案
max()
: 获取集合中最大的元素,若为空元素集合,则返回nullmaxBy{...}
: 获取方法处理后返回结果最大值对应那个元素的初始值,若是没有则返回nullmin()
: 获取集合中最小的元素,若为空元素集合,则返回nullminBy{...}
: 获取方法处理后返回结果最小值对应那个元素的初始值,若是没有则返回nullsum()
: 对集合原元素数据进行累加,返回值类型是IntsumBy{...}
: 根据元素运算操做后的结果进行累加,返回值类型是IntsumByDouble{...}
: 和sumBy{}
类似,但返回值类型是Doubleaverage()
: 对集合求平均数reduce{...}
: 从集合中的第一个元素到最后一个元素的累计操做reduceIndexed{...}
: 在reduce{}
函数基础上多了一个下标功能reduceRight{...}
: 与reduce{...}
相反,该方法是从最后一个元素开始reduceRightIndexed{...}
: 在reduceRight{}
函数基础上多了一个下标功能fold{...}
: 和reduce{}
相似,可是fold{}
有一个初始值foldIndexed{...}
: 和reduceIndexed{}
相似,可是foldIndexed{}
有一个初始值foldRight{...}
: 和reduceRight{}
相似,可是foldRight{}
有一个初始值foldRightIndexed{...}
: 和reduceRightIndexed{}
相似,可是foldRightIndexed{}
有一个初始值any{...}
: 判断集合中是否存在知足条件的元素all{...}
: 判断集合中的全部元素是否都知足条件none{...}
: 和all{...}
函数的做用相反不能过于依赖Kotlin的统计方法,这些方法更适合一些业务逻辑上的简单统计处理,不适合数据统计功能。
注意sum函数返回结果是Int类型,若是是Double则须要用sumByDouble方法。
val persons = mutableListOf(Person("n1", 20, 2000.0), Person("n2", 24, 4000.0), Person("n3", 28, 6000.0), Person("n4", 26, 8000.0), Person("n5", 34, 7000.0), Person("n6", 44, 5000.0)) println("maxBy : ${persons.maxBy { it.age }}") println("sumByDouble : ${persons.sumByDouble { it.salary }}") println("average : ${persons.map { it.salary }.average()}") println("any : ${persons.any { it.salary < 1000 }}") ----------------------------------------------------- maxBy : Person(name=n6, age=44, salary=5000.0) sumByDouble : 32000.0 average : 5333.333333333333 any : false
Kotlin提供了一个遍历集合的forEach方法,也提供了对集合每一个元素都进行指定操做并返回一个新集合的map方法。map方法是能够遍历集合,但若是误将其认为遍历集合的方法,一样会将mapNotNull方法误觉得成遍历非null元素的方法。
map{...}
: 把每一个元素按照特定的方法进行转换,并返回一个新的集合mapNotNull{...}
: 同map{}
相同,过滤掉转换以后为null的元素mapIndexed{index,result}
: 在map{}
函数上多了一个下标功能mapIndexedNotNull{index,result}
: 在mapNotNull{}
函数上多了一个下标功能flatMap{...}
: 根据条件合并两个集合,组成一个新的集合groupBy{...}
: 分组。即根据条件把集合拆分为为一个Map<K,List<T>>
类型的集合map方法不是集合遍历,集合遍历的方法是forEach。
mapNotNull方法不是遍历集合不为null的方法,而是过滤转换后为null的元素。
调用string.split()函数,不管用forEach仍是map,即便没有内容仍是会遍历一次。
val list = listOf(-3,-2,1,3,5,3,7,2,10,9) list.map { it + 1 }.forEach { print("$it \t") } list.mapIndexedNotNull { index, value -> if (index % 2 == 0) value else null }.forEach { print("$it \t") } println("flatMap : ${list.flatMap { listOf(it, it + 1,"n$it") }}") println("groupBy : ${list.groupBy { if (it % 2 == 0) "偶数" else "奇数" }}")
对集合的求交差集是一个经常使用的方法。好比前端须要将更新,建立,删除的逻辑用一个接口完成。咱们能够经过旧数据与新数据求差集找出须要删除的数据。经过新数据和旧数据求差集找出须要建立的数据。经过求交集找出须要更新的数据。
intersect(...)
: 返回一个集合,其中包含此集合和指定集合所包含的全部元素,交集subtract(...)
: 返回一个集合,其中包含此数组包含但未包含在指定集合中的全部元素,差集union(...)
: 返回包含两个集合中全部不一样元素的集合,并集minus(...)
: 返回包含原始集合的全部元素的列表,但给定的数组中包含的元素除外,补集val list1 = mutableListOf(1,2,3,4,5) val list2 = mutableListOf(4,5,6,7) println("intersect : ${list1.intersect(list2)}") println("subtract : ${list1.subtract(list2)}") println("union : ${list1.union(list2)}") println("minus : ${list1.minus(list2)}") ----------------------------------------------------- intersect : [4, 5] subtract : [1, 2, 3] union : [1, 2, 3, 4, 5, 6, 7] minus : [1, 2, 3]
官网地址:https://kotlinlang.org/api/la...
到这里文章就结束了。若是用好集合的高阶函数,可让咱们的开发效率有明显的提升,bug的数量也会锐减。文章还有一部份内容没有介绍。我在工做用中集合就用MutableList、MutableSet、MutableMap,可Java中还有ArrayList,LinkedList,HashMap,HashSet等集合Kotlin中也有这些。一直都没有好好研究,这个坑先挖好,后来再补上。