Kotlin学习之常用高阶函数:查找
在一组数据内查找满足特定条件的元素,或在有索引的一组数据中查找满足特定条件元素的索引,都是十分常用的操作,Kotlin为集合、数组、序列、区间等常用的数据结构提供了一些查找元素的高阶函数。
一、firstOrNull()与lastOrNull()
firstOrNull:从前向后查找特定的元素,如果找到满足条件时的元素就返回这个元素,如果没有找到就返回null。
lastOrNull:从后向前查找特定的元素,如果找到满足条件时的元素就返回这个元素,如果没有找到就返回null。
Kotlin同时也提供了函数名比较短的find()和findLast()函数来简写这两个函数,与firstOrNull()与lastOrNull()作用完全相同。
Array<out T>.lastOrNull()
函数的实现:
inline fun <T> Array<out T>.lastOrNull(predicate: (T) -> Boolean): T? {
for (index in this.indices.reversed()) {
val element = this[index]
if (predicate(element))
return element
}
return null
}
Kotlin这里用Array.indices.reversed()函数来实现从后向前的查找,reversed()函数最终通过调用java.util.Collections.reverse()函数实现反转。
与此类似,firstOrNull()函数从前向后遍历数组,对每个元素调用传入的Boolean函数predicate,如果predicate返回true则返回这个元素,如果没有元素符合条件则返回null。
因为这两个函数都可能返回null,在使用时可以配合Elvis操作符来处理查找不到合适元素的情况:
val array = intArrayOf(0, 1, 2, 3)
val findOdd = array.firstOrNull { it % 2 == 1 } ?: -1
println(findOdd)
通过firstOrNull()函数查找array钟第一个奇数,如果没有找到则返回-1。
firstOrNull()函数返回Int?,这是一个可空类型,但我们在它后面用了Elvis操作符,处理了它为null时的情况,这时候Kotlin编译器就会很聪明地把findOdd类型推断为不可空的Int类型,我们在这之后调用findOdd的方法就不用再担心空安全的问题了。
二、first()与last()
first():用来取第一个元素;
last():用来取最后一个元素。
这两个函数功能与firstOrNull()和lastOrNull()相同,唯一不同的是,这两个函数会在查找不到元素时抛出一个NoSuchElementException异常:
// Array<out T>.first()
throw NoSuchElementException("Array contains no element matching the predicate.")
因为不会返回null,可以在保证能找到元素的情况下使用这两个函数,避免空检查;但在不保证能找到元素时不建议使用,毕竟处理null比处理异常要简单的多。
三、indexOfFirst()与indexOfLast()
这两个函数返回的是满足特定条件的元素的索引,如果没有找到满足条件的元素则返回-1。
Kotlin为数组、集合、区间等数据结构扩展了indexOf()和lastIndexOf()函数,它们都能查找特定元素在一组数据中的索引,那indexOfFirst()和indexOfLast()函数可以视为这两个函数的加强版,它们能根据复杂的条件查找特定的元素索引。
四、maxBy()与minBy()
Kotlin为数组、集合、区间等数据结构扩展了max()和min()函数,能够找到最大和最小的元素,但它们只适用于实现了Comparable接口的类型,也只支持默认的排序方式。
如果数据类型没有实现Comparable类型,或不希望使用默认的排序方式,就可以用maxBy/minBy+Lambda表达式的方法。
Array<out T>.maxBy()
的实现:
inline fun <T, R : Comparable<R>> Array<out T>.maxBy(selector: (T) -> R): T? {
if (isEmpty()) return null
var maxElem = this[0]
var maxValue = selector(maxElem)
for (i in 1..lastIndex) {
val e = this[i]
val v = selector(e)
if (maxValue < v) {
maxElem = e
maxValue = v
}
}
return maxElem
}
可以注意到以下两点:
- 当数组为空时返回null,因此需要空处理;
- 传入的selector函数,返回值类型必须实现Comparable接口,maxBy()函数可以视为map(selector).max(),即先将元素映射为Comparable集合,再调用max()函数找到最大值。
在实际应用中,可以取一个复杂对象的某一个属性来作为排序的依据:
class Person(val name: String, var age: Int)
fun main(vararg args: String) {
val people = arrayOf(
Person("Alex", 19),
Person("Bob", 34),
Person("Candy", 23)
)
val oldest = people.maxBy { it.age }!!
println("Oldest one is ${oldest.name}")
}
上面的例子中使用了非空操作符!!,它的真正作用就是告诉编译器,这里绝对不会是null。
五、single()和singleOrNull()
同样是查找满足特定条件的元素,这两个函数还要求满足条件的元素有且只有一个。如果不存在满足条件的元素或者满足条件的元素超过一个,single()函数会抛出异常,singleOrNull()函数则会返回null。
Array<out T>.single()
函数的实现:
inline fun <T> Array<out T>.single(predicate: (T) -> Boolean): T {
var single: T? = null
var found = false
for (element in this) {
if (predicate(element)) {
if (found) throw IllegalArgumentException("Array contains more than one matching element.")
single = element
found = true
}
}
if (!found) throw NoSuchElementException("Array contains no element matching the predicate.")
return single as T
}
可以看到,如果不止一个元素满足predicate函数,single()函数会抛出IllegalArgumentException,如果不存在元素满足predicate函数,则抛出NoSuchElementException。
在查找具有唯一性的数据时,可以用这两个函数。
single():适用于确保能查找到的情况,可以避免空检查;
singleOrNull():适用于不确保能查找到的情况,需要处理null。
学海无涯苦作舟
朋友们可以关注我的微信公众号,一起进步!!!
Android成长录.jpg
网友评论