本文由 Prefert 發表在 ScalaCool 團隊博客。html
在 Java/Android 開發中,咱們常常用集合來處理數據。java
Java 中的集合相對而言是比較簡單的,可是在不少時候,語法顯得冗長。git
咱們以文章(Article
)爲例子,一篇文章有一個標題、做者及多個標籤:github
public class Article {
private String title;
private String author;
private List<String> tags;
// ... some get、set、construct method
}
複製代碼
如今有一個需求:將全部文章(Article
)按做者(author
)進行分組。編程
Java 實現以下:閉包
private static Map<String, List<Article>> groupByAuthor(List<Article> articles) {
Map<String, List<Article>> result = new HashMap<>();
for (Article article : articles) {
if (result.containsKey(article.getAuthor())) {
result.get(article.getAuthor()).add(article);
} else {
ArrayList<Article> articlesTemp = new ArrayList<>();
articlesTemp.add(article);
result.put(article.getAuthor(), articlesTemp);
}
}
return result;
}
複製代碼
Kotlin 因爲高度兼容 Java 而愈來愈受歡迎,最重要的仍是它簡潔的語法(本篇僅論集合層面),上面的代碼在 Kotlin 中能夠寫爲:oracle
private fun groupByAuthorKotlin(articles: List<Article>): Map<String, List<Article>> {
return articles.groupBy { it.author }
}
複製代碼
鏈式調用是否是很優雅?框架
使用 Java 8 的同窗可能會表示不服(鏈式調用我也行!):ide
private static Map<String, List<Article>> groupByAuthorStream(List<Article> articles) {
return articles.stream()
.collect(Collectors.groupingBy(Article::getAuthor));
}
複製代碼
除了代碼量上的優點,語法上也更能體現業務需求,便於維護。這也是愈來愈多的開發者喜歡函數式的緣由之一。(關於 Stream 與 Kotlin 的對決將呈如今文章後半部分)函數式編程
以上,相信你已經對 Kotlin 集合產生興趣了,接下去讓咱們一塊兒來看看 Kotlin 集合的結構。
咱們都知道 Kotlin 集合基於 (www.tutorialspoint.com/java/java_c…)[Java 集合框架]。
理所應當,它的核心也是 Iterator
。
做爲一個 Java 開發者,咱們都知道 Iterator
主要的做用就是提供遍歷的能力。
可是,Kotlin 將集合分紅了兩類: 「可變集合」 與 「不可變集合」。形成Iterator
層級核心變更以下:
ListIterator
僅支持遍歷。MutableIterator
提供刪除元素的能力。MutableListIterator
繼承以上兩個接口,具有了新增元素的能力即:
Hint: Kotlin 中
out
關鍵字表明這個類的對象爲只讀。
由以上,咱們也能夠推測出,List
以及Set
的結構變更,最關鍵且惟一的變化就是區分了可變集合。
總體結構能夠參考下圖:
與 Java 相比,Kotlin 集合的層次結構更加詳細——這也是 Java 摸爬滾打產生的更好的實踐。
若是你使用過 RxJava 等一系列庫,你必定會對操做符很是瞭解也對操做符的強大深有感觸。
Kotlin 也如此,原生便支持大量操做符,先上一部分感覺一下:
分類 | 方法 |
---|---|
元素操做 | contains / elementAt / firstOrNull / lastOrNull / indexOf / singleOrNull |
判斷操做 | any / all / none / count / reduce / fold |
過濾操做 | filter / filterNot / filterNotNull / take / min / max |
集合轉換 | map / mapIndexed / mapNotNull / flatMap / groupBy / zip |
排序 | reversed / sorted / sortedBy / sortedDescending |
Hint:能夠在 _Collections.kt 中看到全部的操做符。
Talk is cheap ! 咱們舉幾個例子:
filter
與變換 map
// 定義並初始化列表
val list = listOf(1, 2, 3, 4, 5, 6)
println(list.filter { it % 2 == 0 })
// [2, 4, 6]
println(list.map { it * it })
// [1, 4, 9, 16, 25, 36]
複製代碼
觀察結果可知:
filter
函數遍歷集合並返回了符合條件元素的集合。
map
函數遍歷集合並對每一個元素作出了相同的處理。
flatten
與變換平鋪 flatMap
val words = listOf(listOf("kotlin"), listOf("is", "best"))
println(words.flatten())
// [kotlin, is, best]
println(words.flatMap { it.map(String::toUpperCase) })
// [KOTLIN, IS, BEST]
複製代碼
觀察結果可知:
flatten
函數能夠將多個列表形式的元素平鋪,就好像給每一個元素脫掉了衣服,再將他們包在一塊兒。
flatMap
函數但是說是 flatten 的增強版,能夠先將子列表進行變換後再平鋪,再將他們包在一塊兒。
對於沒有接觸過函數式編程的朋友,可能會不由發問: Kotlin 爲何可以實現這樣的騷操做?
這些方法咱們從最簡單的 filter
入手。
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterTo(destination: C, predicate: (T) -> Boolean): C {
for (element in this) if (predicate(element)) destination.add(element)
return destination
}
複製代碼
以上,不難看出 Kotlin 中集合操做符本質上就是方法調用。
filter
實際上是 Itrable
的一個擴展方法 (extention
),它接收一個 T 做爲參數,並返回 Boolean
的閉包做爲參數,內部調用了 filterTo
方法。
再看看 filterTo
方法:傳入了目標類型 C 和判斷用閉包。內部實際就是循環對元素判斷,符合則添加到返回的集合中。
是否是很簡單?
咱們嘗試實現相似 map
和 filter
結合的方法 magicConvert
。
private fun <T, E> Iterable<T>.collect(function: (T) -> E, predicate: (T) -> Boolean): MutableList<E> {
val result: MutableList<E> = mutableListOf()
for (element in this) if(predicate(element)) result.add(function(element))
return result
}
// Test
println(list.collect({ it * it }, { it % 2 == 0 }))
// [4, 16, 36]
複製代碼
至此,咱們應該已經對 Kotlin 集合的操做有了基本瞭解。
對於使用過 RxJava 的你,必定對 Java Stream有所瞭解。
文章開頭的例子已經展現過,在 Java 8 中, stream()
方法使得 Java 傳統的 Collection 類擁有了函數式的操做。
這種語法相較 Kotlin 來講稍微顯得繁瑣了一點,每次操做前都須要轉換成 stream
,操做完還要 調用 collect()
轉換回 Collection。
例如:
// Java
someList
.stream()
.map() // some operations
.collect(Collectors.toList());
複製代碼
// Kotlin
someList
.map() // some operations
複製代碼
可是這麼作,實際上是有緣由:stream 只能被消費一次,不可屢次重用。
下面這樣的操做會拋出異常:
Stream<Integer> someIntegers = integers.stream();
someIntegers.forEach(...);
someIntegers.forEach(...); // an exception
複製代碼
Kotlin 中由於 操做的中間狀態被快速地分配給了變量 ,運行起來並無任何問題。
Java 8 Stream 一個關鍵的點是:它使用了惰性求值(Lazy Evaluation),即在須要的時候纔會求值。
而 Kotlin 則相反(除了 sequences
,將在 Lambda
章節講述),採用及早求值(Eager Evaluation)。
舉個例子:
val result = listOf(1, 2, 3, 4, 5)
.map { n -> n * n }
.filter { n -> n < 10 }
.first()
複製代碼
以上代碼,在 Kotlin 的版本中將執行 5 次 map()
和 filter()
操做,最後返回第一個值。而在 Java Stream 中集合操做只會各執行 1 次。
在對性能有要求的場景下,咱們須要 使用 asSequence()
方法將集合轉爲惰性序列,以最小開銷來實現業務。
Java Stream 的中間操做與 Kotlin 幾乎沒有差異。
須要注意的幾個點是:
peek()
方法用於不間斷的迭代 Stream 流。flatMap()
方法須要返回 Stream 實例(須要用 Arrays.toStream()
處理),而 Kotlin 能夠返回任何類型zip ()
、unzip()
、associate()
操做。本篇文章簡述了 Kotlin 集合的結構,揭露集合操做符的部分本質 並 初探擴展函數。
其次,經過與 Java 8 Stream 的比較,咱們能感覺到 Kotlin 以及函數式編程的優點與魅力。
固然,Kotlin 的黑魔法不止於此。
下一篇,咱們將討論 Kotlin 中的泛型和協變。
參考: