写给开发者Kotlin指引(二)

上一篇说明了Kotlin的基本语法,本节将深入讲解lambda函数,这是Kotlin中使用最频繁的进阶技巧。

一、Lambda函数

1.1 基本用法

Lambda函数对于C++开发者或者Python不用多解释了,Java目前也加入了Lambda的支持。简单来说,Lambda函数就是匿名函数,他可以将函数当成参数传入其他的函数调用中。在C语言中,通过函数指针来实现了这一功能,而在C++中,除了兼容C的函数指针外,还引入了std::function的标准库支持,另外在Modern C++中还加入了lambda的支持。得益于C++丰富的语意支持,lambda函数的功能异常的丰富,但是也加大了使用的难度。

Kotlin中,lambda函数的语法非常简单:

{param1:param1Type param2:param2Type -> functionImplementation}

现在通过一个例子说明lambda函数的用法,假设我们需要找到一个字符串数组中长度最大的一个字符串:

fun sortArray(): String? {
    val list = listOf("zaabb", "aa", "aaazz")
    val lambdaFunc = { s: String -> s.length }
    return list.maxByOrNull(lambdaFunc)
}

这里仔细讲解一下,该函数的返回值是String,后面的?表示该函数可能返回一个null值,listOf的定义如下:

public fun <T> listOf(vararg elements: T): List<T> = if (elements.size > 0) elements.asList() else emptyList()

它是一个泛型函数,我们在调用时需要提供一个类型T,该类型就是listOf函数的参数类型,vararg表示变长参数,我们可以在这里提供多个T类型的参数。我们在调用时的标准写法应该是listOf<String>("zaabb", "aa", "aaazz"),但是泛型函数拥有推断能力,由于我们提供的三个参数都是String类型,所以在调用时可以省略<>。该函数的函数体很简单,就是将边长参数的所有elements转化为一个List<T>

List<T>拥有一个maxByOrNull的方法,来看它的定义:

@SinceKotlin("1.4")
public inline fun <T, R : Comparable<R>> Iterable<T>.maxByOrNull(selector: (T) -> R): T? {
    val iterator = iterator()
    if (!iterator.hasNext()) return null
    var maxElem = iterator.next()
    if (!iterator.hasNext()) return maxElem
    var maxValue = selector(maxElem)
    do {
        val e = iterator.next()
        val v = selector(e)
        if (maxValue < v) {
            maxElem = e
            maxValue = v
        }
    } while (iterator.hasNext())
    return maxElem
}

这是一个内联函数,这个概念在C++中同样存在,就是会将函数在调用处完全展开(C++也不一定会完全展开,编译器会做一些判断,不过用户可以通过一些编译器选项来强制展开)。<T, R : Comparable<R>>指定了该函数在调用时需要提供哪些类型,而R : Comparable<R>表示第二个类型R的类型必须是Comparable<R>的子类。Iterable<T>.表示maxByOrNull函数是Iterable<T>的一个扩展函数。扩展函数是Kotlin中的一个新概念,旨在减少Util类的使用。因为Iterable<T>本身没有定义maxByOrNull方法,所以只要在这里补充定义一下,那么就等于Iterable<T>中定义了一个public的成员方法。接下来分析该函数的参数,selector的类型看上去跟lambda函数的语法非常相似,其实确实是这样,括号中表示该函数接受的参数,为T,而R则表示该函数的返回类型,也就是一个Comparable<R>对象。最后的返回类型是T?,表示返回的可能是T类型的变量,也有可能是个null

在分析函数体之前,先想一下为什么List<T>类型的对象能够调用Iterable<T>的扩展函数maxByOrNull?根据我们对面向对象的理解,猜测List应该是Iterable的子类,通过追踪Kotlin源代码,发现List继承于Collection类,而Collection类继承于Iterable类,说明List是可迭代的。

下面看函数体,第一行调用了iterator(),它的定义是:public operator fun iterator(): Iterator<T>,用于返回一个可迭代对象的迭代器,operator用于标记该函数是一个可重载的运算符函数,这里暂时不细说。iterator有几个成员函数,用于在可迭代对象中移动迭代器。其中hasNext用于判断迭代器是否挪到了终点,而next则指使迭代器挪到可迭代对象中的下一个元素上。这种模式在C++中经常在代码层使用,所以C++开发者应该能够轻易理解这里的意思。不理解的,可以认为就是一个for循环对于List的遍历。了解了这些细节之后,函数的逻辑就很好理解了,就是通过遍历List中的所有元素,并通过selector函数将这些元素都转化为Comparable<R>对象,然后使用<来比较他们的大小,并将大的暂时存储到一个临时变量中,整个算法的复杂度是O(N)。顺便提一下,Comparable<R>中的<号也是一个operator,这就涉及到运算符重载的问题,C++开发者应该能很快领悟其中的奥妙。

1.2 简化调用

其实lambda函数的调用并不需要先声明出来,所以,上面的代码可以简化为:

fun sortArray(): String? {
    val list = listOf("zaabb", "aa", "aaazz")
    return list.maxByOrNull({ s: String -> s.length })
}

Kotlin还规定,当函数的参数的最后一个参数是函数时,可以将函数参数挪到外面:

fun sortArray(): String? {
    val list = listOf("zaabb", "aa", "aaazz")
    return list.maxByOrNull(){ s: String -> s.length }
}

当函数只有一个参数,且该参数是函数时,可以省略调函数调用符号,也就是括号:

fun sortArray(): String? {
    val list = listOf("zaabb", "aa", "aaazz")
    return list.maxByOrNull{ s: String -> s.length }
}

由于类型推导的存在,我们同样也可以省略s后面的参数类型:

fun sortArray(): String? {
    val list = listOf("zaabb", "aa", "aaazz")
    return list.maxByOrNull{ s -> s.length }
}

最后,如果作为参数的函数签名只有一个参数,那么连前面的参数名都可以省略,直接用it代替即可:

fun sortArray(): String? {
    val list = listOf("zaabb", "aa", "aaazz")
    return list.maxByOrNull{ it.length }
}

二、总结

lambda函数大大简化了开发中的冗余代码,而Kotlin的简洁高效也开始初步展现。(C++:什么叫编程语言的皇冠啊😄)

Last updated

Was this helpful?