一次性討論清楚Kotlin的map和flatMap

語言: CN / TW / HK

持續創作,加速成長!這是我參與「掘金日新計劃 · 6 月更文挑戰」的第9天,點選檢視活動詳情

Kotlin 的官方擴充套件函式一直給我的感覺就是:簡單、好用還呈現一種“只有你想不到沒有你找不到”的態勢。

今天咱不聊多麼複雜的,就來談談 mapflatMap,名字很像,到底這兩個貨有什麼區別呢?分別面對的又是什麼使用場景?

概述

首先,名字即功能,mapflatMap是為實現“對映”而存在的,官網是這麼描述對映的:

The mapping transformation creates a collection from the results of a function on the elements of another collection.

意思就是:提供一個轉換函式,用以把一個集合的元素轉化生成另一個集合,而其中最為基礎的就是 map

其次,這兩個不是單屬於某個特定型別的擴充套件,它們寫出來就是面向所有可能需要有“對映”需求的地方的。不信來看看:

```kotlin // 陣列擴充套件 public inline fun Array.map(transform: (T) -> R): List { return mapTo(ArrayList(size), transform) }

// 迭代類擴充套件 public inline fun Iterable.map(transform: (T) -> R): List { return mapTo(ArrayList(collectionSizeOrDefault(10)), transform) }

// 對映擴充套件 public inline fun Map.map(transform: (Map.Entry) -> R): List { return mapTo(ArrayList(size), transform) } ```

都是泛型實現,涵蓋的型別可真不少,甚至還有“對映的對映”這樣的。像Iterable<T>這樣的,一擴充套件,支援的型別那可多了,所有的Collections<T>型別都是,比如ListSet;另外,Kotlin 的 Range 型別也沒落下。

kotlin fun supportNeverEnough() { arrayOf<Int>().map { } listOf<String>().map { } hashMapOf<Int, String>().map { } setOf<Int>().map { } (0 until 10).map { } // 好了,不繼續寫了…… }

當然,flatMap 也是一樣的。

而且值得注意的是,不管是哪個map,其結果都是List型別

map

map 接受傳入的轉換函式,處理後,即將源轉化成一個新的List,且這個新的List的元素順序和其源是一致的

對於“陣列”類的源(比如list, set, array等),map 為:

kotlin fun <T, R> XXX<T>.map(transform: (T) -> R): List<R>

可以看到,map就是將T型別的集合轉化成了R型別的List

陣列型別

```kotlin public inline fun Array.map(transform: (T) -> R): List { return mapTo(ArrayList(size), transform) }

public inline fun > Array.mapTo(destination: C, transform: (T) -> R): C { for (item in this) destination.add(transform(item)) return destination } ```

map 內部會呼叫 Array<out T>.mapTo 方法,該方法第一個引數是 MutableCollection<in R> 的子型別,即可變集合,用來迭代儲存結果元素。

第二個引數就是轉換函式,將T型別轉為R型別。

內部 for 迴圈迭代所有元素,每個元素呼叫轉換函式,生成結果並新增至集合,最後返回。整個過程算是十分簡單了。

迭代集合型別

這是針對迭代集合 Iterable 的:

```kotlin public inline fun Iterable.map(transform: (T) -> R): List { return mapTo(ArrayList(collectionSizeOrDefault(10)), transform) }

internal fun Iterable.collectionSizeOrDefault(default: Int): Int = if (this is Collection<*>) this.size else default

public inline fun > Iterable.mapTo(destination: C, transform: (T) -> R): C { for (item in this) destination.add(transform(item)) return destination } ```

看起來陣列的map很類似,但這裡多了一個 collectionSizeOrDefault 方法,這是什麼用意呢?

很好理解:相當於可以預先設定最終容器的大小。因為有可能此 Iterable<T> 型別不是 Collection,無法獲取 size,所以加了個判斷,如果無法獲取,則預設一個 size 為 10.

對映

Map 對映的 map

```kotlin public inline fun Map.map(transform: (Map.Entry) -> R): List { return mapTo(ArrayList(size), transform) }

public inline fun > Map.mapTo(destination: C, transform: (Map.Entry) -> R): C { for (item in this) destination.add(transform(item)) return destination } ```

因為是 Map,所以泛型為 , 類似陣列和迭代集合,這裡只是把迭代轉換的引數換成了 MapEntry

flatMap

同樣的,flatMap 也支援前面提到的所有型別:

kotlin fun supportNeverEnoughForFlatMap() { arrayOf<Int>().flatMap { 0..it } listOf<String>().flatMap { 0..it.length } hashMapOf<Int, String>().flatMap { 0..it.value.length } setOf<Int>().flatMap { 0..it } (0 until 10).flatMap { 0..it } }

map 的區別在於,flatMap 的轉換函式型別是: transform: (T) -> Iterable<R> ,即輸入T型別,得到R型別的迭代集合。

所以說,map轉換是一到一,flatMap則是一到多,但是最終,flatMap得到的還是一個R的集合

陣列型別

```kotlin public inline fun Array.flatMap(transform: (T) -> Iterable): List { return flatMapTo(ArrayList(), transform) }

public inline fun > Array.flatMapTo(destination: C, transform: (T) -> Iterable): C { for (element in this) { val list = transform(element) destination.addAll(list) } return destination } ```

原始碼看起來和 map 是很像的,關鍵的不同處在於:

kotlin destination.addAll(list)

因為對映結果是集合,所以這裡呼叫的是addAll。雖然一個item得到一個集合,但最後返回值不是集合的集合,仍然是單集合 —— 很繞嗎?不,這就是flat這個字首存在的意義:扁平化。

集合型別

```kotlin public inline fun Iterable.flatMap(transform: (T) -> Iterable): List { return flatMapTo(ArrayList(), transform) }

public inline fun > Iterable.flatMapTo(destination: C, transform: (T) -> Iterable): C { for (element in this) { val list = transform(element) destination.addAll(list) } return destination } ```

和陣列如出一轍,不用細講了。

對映

```kotlin public inline fun Map.flatMap(transform: (Map.Entry) -> Iterable): List { return flatMapTo(ArrayList(), transform) }

public inline fun > Map.flatMapTo(destination: C, transform: (Map.Entry) -> Iterable): C { for (element in this) { val list = transform(element) destination.addAll(list) } return destination } ```

同樣的配方。

總有個But

但是!!!雖然不細講,細心的人還是能發現: flatMap 初始化集合全都沒指定大小 —— 因為“一到多” 的對映操作,根本無法預估最終的集合大小啊是不?

小結

看到這裡,相信 mapflatMap 的區別已經很清楚了吧,簡單地就如前面所說:二者區別在於轉換函式,前者“一到一”,後者“一到多”。而它們的返回型別,都是一模一樣的。

媽媽終於不用操心我的對映操作會用錯了!