初識Kotlin之集合

Kotlin的集合是讓我爲之心動的地方,豐富的高階函數幫助我們高效開發。今天介紹Kotlin的基礎集合用法、獲取集合元素的函數、過濾元素的函數、元素排序的函數、元素統計的函數、集合元素映射的函數、集合的交差並補集的函數。還有一些工作中的經驗。

先睹爲快

批量更新、創建、刪除功能

需求:前端有一個二維表格,希望後端提供一個支持批量更新、創建、刪除功能的接口。且對部分字段的值有特殊要求。

分析:這樣的需求並不少見,如工廠車間的能耗統計。統計的是每個車間,每臺設備的能耗值。這些值是可以被用戶手動維護的。且這些值都是有取值範圍。

1)、特殊字段攔截:如果是一條數據的操作,可以通過註解對字段進行校驗。但是批量操作,要考慮事務回滾帶來的沒必要的開銷。可以考慮用代碼進行特殊字段的過濾。

2)、區分創建、更新和刪除:一個接口完成三個操作,必須要清楚哪些數據。我們可以通過是否有id來區分更新和創建。通過舊數據和新數據求差集區分刪除。

下面是一段僞代碼,爲了方便演示集合的函數,一些方法都放在一起介紹。

@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)、首先接口是可以獨立完成業務邏輯。調用者並不需要關係業務邏輯,只需按照給定的參數發送請求,就可以獲取想要的結果。

2)、其次接口是有較強的健壯能力。後端的業務邏輯不能因爲調用者的錯誤請求,而報出500的錯誤,至少也是已知的業務錯誤。

3)、最後接口應該儘量避免級聯刪數據功能。所有的刪除操作儘可能甩鍋給用戶。

隨着前端功能越來越強大,前後端在處理接口的問題上矛盾也是越來越多。一些後端處理的邏輯都開始交給前端(部分前端開始膨脹了,部分後端開始偷懶了)。這導致一些分工的不明確,甚至一些本該由後端處理的邏輯也交給了前端。似乎在他們眼裏,後端就是數據的crud。好在這樣的後端大多都比較年輕,也許後期會成長起來。只能心疼一下前端。

對於我而言,不會把主動權交給前端。提供一個健壯、優質的接口是對自己的要求。給小白一些建議:對接口負責,就是對自己負責,也是對其他同事負責。

集合簡介

和Java集合不同的是,Kotlin的集合分可變和不可變兩種集合。同時也支持兩種集合相互切換。

List集合

// 聲明並初始化不可變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集合

// 聲明並初始化不可變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集合

// 聲明並初始化不可變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"字樣的方法。比如我常用的getOrElsefirstOrNull 等方法。分別表示:通過下標如果沒有獲取到值,則返回自定的值。和獲取集合的第一個元素,若集合爲空則返回null。正因爲Kotlin提供了很多類似getOrElsefirstOrNull 的方法。很大程度上提高了我們的開發效率,和減少了一些低級錯誤發生的概率。接下來我們學習一下Kotlin具體有哪些獲取集合元素的方法(single方法沒怎麼用過)

常用函數

  • get(index) : List的函數,通過下標獲取指定元素。若找不到值(下標越界),會拋出IndexOutOfBoundsException異常
  • getOrElse(index, {...}) : List的擴展函數,通過下標獲取指定元素。找不到值則返回默認值
  • getOrNull(index) : List的擴展函數,通過下標獲取指定元素。找不到值則返回null
  • elementAtOrElse(index, {...}) : Iterable接口的擴展函數,功能同getOrElse 方法
  • elementAtOrNull(index) : Iterable接口的擴展函數,功能同getOrNull 方法
  • 注意get方法是List獨有,其他集合可以用element方法。
  • first() : 獲取集合第一個元素。若沒有返回值,則拋出NoSuchElementException異常
  • first{} : 獲取集合中指定元素的第一個元素。若沒有返回值,則拋出NoSuchElementException異常
  • firstOrNull() : 獲取集合第一個元素。若沒有返回值,返回null
  • firstOrNull{} : 獲取集合指定元素的第一個元素。若沒有返回值,返回null
  • 看到這裏,是不是有點明白Kotlin獲取元素的規則:如果沒有則怎麼樣
  • last() : 與first()相反
  • last{} : 與first{}相反
  • lastOrNull{} : 與firstOrNull()相反
  • lastOrNull() : 與firstOrNull{}相反
  • indexOfFirst{...} : 返回集合中第一個滿足條件元素的下標
  • indexOfLast{...} : 返回集合中最後一個滿足條件元素的下標
  • 咋也不知道single方法設計的初衷,咋也不敢問
  • single() : Returns the single element, or throws an exception if the collection is empty or has more than one element. 官方api文檔地址
  • single{} : 按照條件返回單個元素,若集合爲空或者有多個元素滿足條件,則報錯
  • singleOrNull() : 返回單個元素,若集合爲空或者有多個元素,則返回null
  • singleOrNull{} : 按照條件返回單個元素,若集合爲空或者有多個元素滿足條件,則返回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{} : 根據條件給集合降序
  • ed結尾的排序方法,是不會對原集合進行修改,而是返回一個排序後的新集合。沒有以ed結尾的方法恰恰相反 —來自一個不嚴謹的總結
  • 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的 getOrNullsortedBy 函數,並沒有給人一種眼前一亮的感覺。但它提高了代碼的可讀性和美觀性。

常用函數

  • filter{...} : 過濾不滿足條件的元素,返回只滿足條件元素列表,不影響原集合
  • filterNot{...} : 和filter{}函數的功能相反
  • filterNotNull() : 過濾掉集合中爲null的元素
  • filterIndexed{...} : 在filter{}函數上多了一個下標功能,可以通過索引進一步過濾
  • Kotlin的函數是見名知意,非常好用,上手也快,弄明白一個方法,其他方法都沒大的問題
  • distinct() : 去除重複元素,返回元素的順序和原集合順序一致
  • distinctBy{...} : 根據操作元素後的結果去去重,去除的是操作前的元素
  • take(num) : 返回集合中前num個元素組成的集合
  • takeWhile{...} : 從第一個元素開始遍歷集合,當出現第一個不滿足條件元素時退出循環。返回所有滿足條件的元素集合
  • takeLast(num) : 和take 函數相反,返回集合中後num個元素組成的集合
  • takeLastWhile{...} : 從最後一個元素開始遍歷集合,當出現第一個不滿足條件元素時退出循環。返回所有滿足條件的元素集合
  • 不要被這麼多方法嚇到,學了take函數的用法,takeLast、drop、dropLast的用法都可以猜到
  • 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() : 獲取集合中最大的元素,若爲空元素集合,則返回null
  • maxBy{...} : 獲取方法處理後返回結果最大值對應那個元素的初始值,如果沒有則返回null
  • min() : 獲取集合中最小的元素,若爲空元素集合,則返回null
  • minBy{...} : 獲取方法處理後返回結果最小值對應那個元素的初始值,如果沒有則返回null
  • sum() : 對集合原元素數據進行累加,返回值類型是Int
  • sumBy{...} : 根據元素運算操作後的結果進行累加,返回值類型是Int
  • sumByDouble{...} : 和sumBy{}相似,但返回值類型是Double
  • average() : 對集合求平均數
  • 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/latest/jvm/stdlib/kotlin.collections/

到這裏文章就結束了。如果用好集合的高階函數,可以讓我們的開發效率有明顯的提高,bug的數量也會銳減。文章還有一部分內容沒有介紹。我在工作用中集合就用MutableList、MutableSet、MutableMap,可Java中還有ArrayList,LinkedList,HashMap,HashSet等集合Kotlin中也有這些。一直都沒有好好研究,這個坑先挖好,後來再補上。

Kotlin的集合是讓我爲之心動的地方,豐富的高階函數幫助我們高效開發。今天介紹Kotlin的基礎集合用法、獲取集合元素的函數、過濾元素的函數、元素排序的函數、元素統計的函數、集合元素映射的函數、集合的交差並補集的函數。還有一些工作中的經驗。

集合簡介

和Java集合不同的是,Kotlin的集合分可變和不可變兩種集合。同時也支持兩種集合相互切換。

List集合

// 聲明並初始化不可變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集合

// 聲明並初始化不可變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集合

// 聲明並初始化不可變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"字樣的方法。比如我常用的getOrElsefirstOrNull 等方法。分別表示:通過下標如果沒有獲取到值,則返回自定的值。和獲取集合的第一個元素,若集合爲空則返回null。正因爲Kotlin提供了很多類似getOrElsefirstOrNull 的方法。很大程度上提高了我們的開發效率,和減少了一些低級錯誤發生的概率。接下來我們學習一下Kotlin具體有哪些獲取集合元素的方法(single方法沒怎麼用過)

常用函數

  • get(index) : List的函數,通過下標獲取指定元素。若找不到值(下標越界),會拋出IndexOutOfBoundsException異常
  • getOrElse(index, {...}) : List的擴展函數,通過下標獲取指定元素。找不到值則返回默認值
  • getOrNull(index) : List的擴展函數,通過下標獲取指定元素。找不到值則返回null
  • elementAtOrElse(index, {...}) : Iterable接口的擴展函數,功能同getOrElse 方法
  • elementAtOrNull(index) : Iterable接口的擴展函數,功能同getOrNull 方法
  • 注意get方法是List獨有,其他集合可以用element方法。
  • first() : 獲取集合第一個元素。若沒有返回值,則拋出NoSuchElementException異常
  • first{} : 獲取集合中指定元素的第一個元素。若沒有返回值,則拋出NoSuchElementException異常
  • firstOrNull() : 獲取集合第一個元素。若沒有返回值,返回null
  • firstOrNull{} : 獲取集合指定元素的第一個元素。若沒有返回值,返回null
  • 看到這裏,是不是有點明白Kotlin獲取元素的規則:如果沒有則怎麼樣
  • last() : 與first()相反
  • last{} : 與first{}相反
  • lastOrNull{} : 與firstOrNull()相反
  • lastOrNull() : 與firstOrNull{}相反
  • indexOfFirst{...} : 返回集合中第一個滿足條件元素的下標
  • indexOfLast{...} : 返回集合中最後一個滿足條件元素的下標
  • 咋也不知道single方法設計的初衷,咋也不敢問
  • single() : Returns the single element, or throws an exception if the collection is empty or has more than one element. 官方api文檔地址
  • single{} : 按照條件返回單個元素,若集合爲空或者有多個元素滿足條件,則報錯
  • singleOrNull() : 返回單個元素,若集合爲空或者有多個元素,則返回null
  • singleOrNull{} : 按照條件返回單個元素,若集合爲空或者有多個元素滿足條件,則返回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{} : 根據條件給集合降序
  • ed結尾的排序方法,是不會對原集合進行修改,而是返回一個排序後的新集合。沒有以ed結尾的方法恰恰相反 —來自一個不嚴謹的總結
  • 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的 getOrNullsortedBy 函數,並沒有給人一種眼前一亮的感覺。但它提高了代碼的可讀性和美觀性。

常用函數

  • filter{...} : 過濾不滿足條件的元素,返回只滿足條件元素列表,不影響原集合
  • filterNot{...} : 和filter{}函數的功能相反
  • filterNotNull() : 過濾掉集合中爲null的元素
  • filterIndexed{...} : 在filter{}函數上多了一個下標功能,可以通過索引進一步過濾
  • Kotlin的函數是見名知意,非常好用,上手也快,弄明白一個方法,其他方法都沒大的問題
  • distinct() : 去除重複元素,返回元素的順序和原集合順序一致
  • distinctBy{...} : 根據操作元素後的結果去去重,去除的是操作前的元素
  • take(num) : 返回集合中前num個元素組成的集合
  • takeWhile{...} : 從第一個元素開始遍歷集合,當出現第一個不滿足條件元素時退出循環。返回所有滿足條件的元素集合
  • takeLast(num) : 和take 函數相反,返回集合中後num個元素組成的集合
  • takeLastWhile{...} : 從最後一個元素開始遍歷集合,當出現第一個不滿足條件元素時退出循環。返回所有滿足條件的元素集合
  • 不要被這麼多方法嚇到,學了take函數的用法,takeLast、drop、dropLast的用法都可以猜到
  • 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() : 獲取集合中最大的元素,若爲空元素集合,則返回null
  • maxBy{...} : 獲取方法處理後返回結果最大值對應那個元素的初始值,如果沒有則返回null
  • min() : 獲取集合中最小的元素,若爲空元素集合,則返回null
  • minBy{...} : 獲取方法處理後返回結果最小值對應那個元素的初始值,如果沒有則返回null
  • sum() : 對集合原元素數據進行累加,返回值類型是Int
  • sumBy{...} : 根據元素運算操作後的結果進行累加,返回值類型是Int
  • sumByDouble{...} : 和sumBy{}相似,但返回值類型是Double
  • average() : 對集合求平均數
  • 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/latest/jvm/stdlib/kotlin.collections/

到這裏文章就結束了。如果用好集合的高階函數,可以讓我們的開發效率有明顯的提高,bug的數量也會銳減。文章還有一部分內容沒有介紹。我在工作用中集合就用MutableList、MutableSet、MutableMap,可Java中還有ArrayList,LinkedList,HashMap,HashSet等集合Kotlin中也有這些。一直都沒有好好研究,這個坑先挖好,後來再補上。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章