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中也有這些。一直都沒有好好研究,這個坑先挖好,後來再補上。