Kotlin之Lambda表达式

·  阅读 733
Kotlin之Lambda表达式

什么是lambda

Lambda表达式,简称lambda,是一种包含代码块的对象,注意,它是一个对象。我们可以将其赋给一个变量,就像其他的类型的对象一样,或者将其传递给一个方法,然后该方法就会执行其包含的代码块。也就是说,你可以使用lambda将特定的行为传递给更通用的方法。

lambda实战

lambda表达式的格式

我们先看看如何去书写一个lambda

{ x: Int -> x + 5 }
复制代码

以上就是一个最简单的lambda表达式,它将Int参数的值加5 。lambda以花括号作为开始和结束。所有的lambda都被定义在花括号中,并且不可以省略

在花括号中,使用x:Int定义了单个Int参数x,lambda可以有单参数,也可以有多个参数或者没有参数。

参数之后是->,这个符号用于隔离参数和主体,后面的就是主体部分,也就是x+5,主体部分可以有多行,其中最后一行将作为lambda表达式的返回值。

我们来写一个有多个参数的lambda吧

{a:Int,b:Int -> a+b}
复制代码

将lambda赋值给变量

我们可以将lambda表达式赋值给变量,就像其他任何对象进行赋值一样,接着上边的例子:

var addFive = { x: Int -> x + 5 }
复制代码

我们定义了一个变量 addFive,在使用lambda对其进行赋值操作的时候,是将代码块赋值给他,而不是将代码的结果赋值给他,要执行lambda中的代码,就需要显示的调用他。

如何进行调用呢?我们可以使用invoke函数调用lambda,并传入参数的值,

var s = addFive.invoke(5)
println(s)
复制代码

输出结果为 10 。

也可以直接进行调用

println(addFive(5))
复制代码

同样输出10 。

当然无论哪种调用方式,最终生成的字节码都是通过invoke方法进行调用的。

image.png

Lambda的表达式类型

同任何其他类型(Int,Double,Float...)一样,lambda也是一种类型,不同点在于,他不会为lambda的实现指定类名,而是指定lambda的参数和返回值类型。 其类型的格式如下

(parameters)-> return_type
复制代码

如果你的lambda具有单独的Int参数并返回一个字符串

var l = {x:Int -> "$x"}
复制代码

那么,这个lambda的类型就是

(Int)-> String
复制代码

如果将lambda赋值给一个变量,编译器会根据该lambda来推测变量的类型。当然我们可以显示的定义该变量的类型,下面我们就来显示的定义一下

//声明变量   指定类型        赋值操作
var v : (Int)->String = {x:Int -> "$x"}
复制代码

lambda参数类型的自动解析

显示声明变量类型的时候,可以在lambda中省略任何编译器能够推断出的类型的声明,如以上的显示声明的操作var v : (Int)->String = {x:Int -> "$x"},其中,编译器已经从 v 的类型定义中得知,任何赋值给该变量的lambda必须有一个Int参数,这就意味着可以在lambda参数定义中省略Int类型的声明,因为编译器可以推断出其类型,我们可以简写为:

var v : (Int)->String = {x -> "$x"}
复制代码

除此之外,如果lambda具有一个单独的参数,并且编译器能够推断出其类型,则可以省略该参数,并在lambda的主体中使用 it 关键字代替它。

还是上面的语句,其中只有一个 Int 类型的参数 x,并且编译器可以推断出其类型,所以我们就可以将该语句简化为

var v : (Int)->String = {"$it"}
复制代码

这里要注意的是:只有在编译器能推断该参数类型的前提条件下,才可以使用 it 语法。如果编译器无法推断,则不能使用,下面就是一个错误的例子:

var v  = {"$it"}
复制代码

将lambda作为函数的参数

除了将lambda赋值给变量,还可以使用一个或者多个lambda作为函数的参数,通过这种方式就允许我们将特定的行为传入其他的函数中。

下面我们就来编写一个带有lambda参数的函数。

//  函数名   Double类型参数   lambda类型的参数           返回值类型
fun convert(x:Double,   converter:(Double)->Double): Double {
    
}
复制代码

以上就将lambda当作参数传入了另外一个函数中。

函数我们定义好了,如何让lambda的参数在新的函数中发挥作用呢?现在我们给convert函数定义一些功能,让其实现一个数据计算的功能。我们将参数 x 传入之后,通过lambda进行计算,并将结果进行返回。

完整的convert函数的逻辑如下:

fun convert(x:Double,converter:(Double)->Double): Double {
    //调用converter的lambda,并将其返回值赋值给result
    val result  = converter(x)
    //返回结果
    return result
}
复制代码

通过以上的分析,我们就可以自己定义这个lambda参数的逻辑,传入函数体,就可以实现相应的需求。

print(convert(10.0, { x:Double-> x+10 } ))
复制代码

以上我们传入了一个简单的lambda表达式,实现加10的逻辑,最终的输入结果为 20.0 。

在这里有一些问题需要重点说一下:如果lambda是函数的最后一个参数,就像上面我们定义的convert,那么可以将lambda参数移动到函数调用的括号外面。

print(convert(10.0) {x:Double->x+10} )
复制代码

如果函数只有一个参数并且该参数为lambda,则可以在调用该函数时省略掉整个括号,我们修改一下上面的convert函数

fun convert(converter:(Double)->Double): Double {
    return converter(10.0)
}
复制代码

现在其只有一个参数,并且参数类型为lambda。我们正常的调用为:

//这样
print(convert( {x:Double -> x+10} ))
//或者这样
print(convert() {x:Double -> x+10} )
复制代码

这里就可以简写为

print(convert {x:Double -> x+10} )
复制代码

将外面的括号去掉。

函数返回值是一个lambda

除了将lambda作为参数,函数还可以通过lambda的类型指定其返回一个lambda。如下:如果传入的x等于0,那么就返回 乘以10 的lambda,否则返回 乘以20的lambda。

fun getLambda(x:Int):(Int)->Int {
    if(x == 0){
        return {m:Int -> m * 10}
    }else{
        return {it * 20}
    }
}
复制代码

接下来我们来调用一下这个方法。

val m = getLambda(0)
print(m(10))
val n = getLambda(1)
print(n(10))
复制代码

打印的结果为 100和200 。

将lambda作为函数的参数和返回值

现在我们来创建这样一个函数,接收两个lambda参数进行合并,最后返回一个新的lambda。

fun combine(lambda1:(Double)->Double, lambda2:(Double)->Double) : (Double)->Double{
    return {x:Double -> lambda2(lambda1(x))}
}
复制代码

我们来分析一下这个函数,首先该函数有两个参数 lambda1,lambda2 都是lambda类型的,并且最终都返回一个Double类型的值。

函数的返回值也是lambda,不过其中又结合了lambda1和lambda2的lambda,利用这两个lambda来完成最终的计算。

我们来调用一下这个函数。

//首先定义两个lambda的参数
//✖️2的操作
val l1 = {x:Double -> x*2}
//除4的操作
val l2 = {x:Double -> x/4}
//得到一个新的lambda
val combine = combine(l1,l2)
//打印新的lambda的调用结果
println(combine(10.0))
复制代码

最终的打印结果为5.0,现将 10 交给l1进行✖️2,再交给l2 进行➗4,得到5.0 。

让lambda更具可读性-typealias

lambda函数类型的使用,是我们的代码更加繁琐,同时缺乏可读性,例如上面的 combine 函数,lambda中嵌套使用lambda,就会出现很多的函数类型的引用。针对这种情况,我们可以通过 类型别名 替代函数类型,使得代码更具有可读性。

类型别名允许为已经存在的类型取一个替代名,然后就可以在代码中使用了。关键字typealias 就用来定义别名,通过这样的定义,就可以简化我们的代码,我们来修改一下上面的combine函数。

首先我们定义类型别名:

typealias balabala = (Double)->Double
复制代码

使用类型别名进行替换操作:

fun combine(lambda1:balabala, lambda2:balabala) : balabala{
    return {x:Double -> lambda2(lambda1(x))}
}
复制代码

编译器看到 balabala 时,就会知道这就是一个 (Double)->Double的占位符。

结束语

以上我们介绍了关于lambda的一些知识点,lambda的使用还是非常广泛的,它可以赋值给变量,可以是一种类型,可以当作函数的参数和返回值等等。

lambda是函数式编程的一个重要组成部分,非函数式编程读取输入数据并产生输出数据,而函数式编程可以将函数作为输入,并产生函数作为输出。理解lambda对于理解函数式编程会更有帮助。