我們已經學習了 Kotlin 中的空安全(null safety)。在本節中,我們將討論如何處理集合中的空值(null),因為集合比其他數據類型更復雜。我們還將討論如何處理可空元素時常用的便利方法。
集合與空值
可空集合和具有可空元素的非空集合是同一枚硬幣的兩面。此外,我們還需要認識到空集合和可空集合之間的區別。讓我們看看四種情況:
val list = listOf<String>()var nullableList: List<Int>? = listOf<Int>(1, 2, 4, 6)val listWithNullableElements: List<Int?> = listOf<Int?>(1, 2, 4, null, null)var absolutelyNullableList: List<Int?>? = listOf<Int?>(1, 2, 4, null, null)
第一種情況:我們有一個簡單的空列表。我們可以像對待常規列表一樣處理它,并不需要擔心空指針異常(NullPointerException)。這個列表是實際的且非空的,只是為空。
第二種情況:我們有一個可空的列表:這樣的列表中的元素不可為空,必須是實際的整數。但變量 nullableList
本身可以為空。在使用可空列表時,我們需要使用安全調用運算符(?.
)、空值合并運算符(?:
)等操作,例如:
val list: List<Int> = nullableList ?: listOf<Int>()
第三種情況:我們有一個具有可空元素的列表。該列表的類型是非空的,但其中的元素可以為空。
val num: Int = listWithNullableElements[1] ?: 150
第四種情況:我們結合了第二種和第三種情況:
val num: Int = absolutelyNullableList?.get(1) ?: 150
基本原則是:如果可以返回一個空集合,最好返回空集合,而不是返回 null
,避免使用可空類型。然而,有時我們確實需要處理可空集合。例如,如果我們聲明了一個可以接收值或 null
的變量(var
而不是 val
),那么 null
就相當于“無元素”,“沒有答案”或“沒有結果”。
從包含空值的序列創建非空集合
有時你會遇到包含空值的元素序列,而你需要使用這些序列創建一個沒有空值的集合。在這種情況下,可以使用特定的函數 listOfNotNull()
和 setOfNotNull()
,它們幫助我們刪除所有空值并返回默認的只讀非空集合。讓我們來看一下它是如何工作的:
val list = listOfNotNull(1, null, 50, 404, 42, null, 42, 404) // [1, 50, 404, 42, 42, 404]
val set = setOfNotNull(1, null, 50, 404, 42, null, 42, 404) // [1, 50, 404, 42]
所有空值元素都被從新集合中刪除。如果你的元素序列只有空值,這些方法將返回一個空集合(非空!)。記住,如果你需要一個可變集合,可以通過 toMutableList()
或 toMutableSet()
將其轉換為可變集合。
可空集合的函數
Kotlin 提供了一些方便的工具來處理具有可空元素的集合:isNullOrEmpty()
、getOrNull()
、firstOrNull()
、lastOrNull()
和 randomOrNull()
。讓我們來看看它們!
isNullOrEmpty()
:如果集合為空或為null
,則返回true
。否則返回false
。
val emptySet: Set<Int>? = setOf()
val nullSet: Set<Int>? = null
val set = setOf<Int?>(null, null)println(emptySet.isNullOrEmpty()) // true,因為集合為空
println(nullSet.isNullOrEmpty()) // true,因為集合為 null
println(set.isNullOrEmpty()) // false,因為集合中有兩個空值元素
getOrNull()
:返回列表或數組中的一個元素,如果該元素不存在,則返回null
(不能用于Set
)。
val list = listOf(0, 1, 2)
println(list.getOrNull(2)) // 2
println(list.getOrNull(3)) // null,因為這個列表沒有第四個元素,索引從 0 開始
你可以使用 list[3]
代替,但這樣會引發異常,而 getOrNull()
則會在任何情況下返回一個值。
randomOrNull()
:像getOrNull()
一樣,如果集合為空,它返回null
,否則返回一個隨機元素。
val list = listOf(0, 1, 2)
val list1 = listOf<Int>()println(list.randomOrNull()) // 返回一個元素
println(list1.randomOrNull()) // null,因為集合為空
firstOrNull()
和 lastOrNull()
:允許我們設置特定的條件。如果集合中至少有一個元素滿足條件,它們會返回該元素。
區別是:
-
firstOrNull()
會返回集合中 第一個滿足條件的元素。如果沒有滿足條件的元素,它會返回null
。 -
lastOrNull()
會返回集合中 最后一個滿足條件的元素。如果沒有滿足條件的元素,它也會返回null
。
val list = listOf(0, 1, 1, 2, 5, 7, 6)
val num = list.firstOrNull { it > 3 }
val num1 = list.lastOrNull { it == 1 }
最小值和最大值(可空)
Kotlin 為集合提供了許多方便的比較工具——包括處理可空元素的工具。以下是它們的簡介:
-
minOrNull()
/maxOrNull()
:返回集合中的最大或最小元素,如果集合為空,則返回null
。 -
minByOrNull()
/maxByOrNull()
:返回滿足條件的最大或最小元素,如果沒有符合條件的元素,則返回null
。 -
minOfOrNull()
/maxOfOrNull()
:返回元素特性(如值、大小等)上的最大或最小值,若集合為空則返回null
。 -
minWithOrNull()
/maxWithOrNull()
:返回滿足條件的最大或最小元素,指定了compareBy {}
塊。 -
minOfWithOrNull()
/maxOfWithOrNull()
:返回符合條件的元素特性上的最大或最小值,指定了compareBy {}
塊。
我們這里只提到這些函數,詳細的示例和講解可以參考“集合的聚合操作”一節。
有一點需要注意:這些函數都有沒有 “OrNull” 后綴的對應版本。曾幾何時,這些“沒有 OrNull” 的函數是合法的工具。但從 Kotlin 1.4.0 開始,這些函數(如 min()
、max()
、minBy()
、maxBy()
、minWith()
、maxWith()
)被重命名為 minOrNull()
、maxOrNull()
等,且老版本的函數已標記為廢棄。到了 Kotlin 1.7.0,這些廢棄的函數重新引入,作為它們各自 “OrNull” 對應版本的非空替代品。這些非空版本返回一個集合元素,或者在集合為空時拋出異常。所以使用時要小心!
結論
我們已經討論了如何處理具有可空元素的集合以及一些便捷的方法。以下是幾個要點:
-
Kotlin 中有可空集合、具有可空元素的集合和空集合,它們是不同的。
-
listOfNotNull()
和setOfNotNull()
函數幫助我們從包含空值的序列中創建非空集合。 -
我們可以檢查集合是否為空,或者集合中是否有元素滿足某些條件,確保不會拋出異常。
-
我們可以使用比較函數如
minOrNull()
、maxOrNull()
等,選擇并顯示集合元素或其特性。 -
這些函數有對應的非空版本,它們在集合為空時會拋出異常,而不是返回
null
。