深刻理解 Kotlin高阶函数、匿名函数以及lambda表达式

在说高阶函数之前,我们得知道什么是函数类型,啊,什嘛?不知道函数类型?那好吧,下面就说说什么是函数类型。

一 、函数类型

在 Kotlin 里面定义变量类型很简单,如下:

var a :Int = 1 // Int 型变量
var person :Person = Person()  // 定义 Person 型变量

那么函数类型应该是什么样的表现形式呢?有的,形式如下:

(X, Y)-> Z,其中 XY 表示函数的参数,当然了,参数的个数任意,这里只写了2个,Z 表示函数的返回值

知道这种形式后,我们可以很快的写出函数类型,如下:

var f :(Int , Int)-> Int

(Int , Int)-> Int 表示函数的参数为2个Int 型,且返回值为 Int 型的函数类型。

注意:参数列表的小括号是必须要的,即使没有参数也不能省略,即写成()-> String;即使函数类型的返回值类型为Unit,也不可省略Unit不写,例如:(Int) -> Unit

函数类型如何赋值呢?

假设我们定义了函数类型如下:

var f :(Int , Int)-> Int

然后定义了一个sum函数如下:

fun sum (a :Int, b :Int) : Int {
   return a + b
}

可以看出来,上面定义的函数类型 fsum 函数的签名和返回值类型都是一样的。
那么我们可以直接像这样的赋值吗 ?

f = sum

这样是不行的,那正确的写法是什么样的呢?

f = ::sum
双冒号(::)加方法名是什么鬼呢?

Kotlin 官方的说法 :双冒号的写法叫做函数引用 (Function Reference)。
但是这又表示什么意思?表示它指向上面的函数?那既然都是一个东西,为什么不直接写函数名,而要加两个冒号呢?

因为加了两个冒号,这个函数才变成了一个对象,只有对象才能被赋值给变量。
但 Kotlin 的函数本身没办法被当做一个对象。那怎么办呢?Kotlin 的选择是,那就创建一个和函数具有相同功能的对象。怎么创建?使用双冒号。

在 Kotlin 里,一个**函数名左边加上双冒号,它就不表示这个函数本身了,而表示一个对象,或者说一个指向对象的引用,但是,这个对象可不是函数本身,而是一个和这个函数具有相同功能的对象。

怎么使用呢?
val f = ::sum
val res1 = f(2, 3) // 输出5
(::sum)(2, 3) // 输出5

还有这样:

f.invoke(2, 3) // 输出5

所以你可以对一个函数类型的对象调用 invoke(),但不能对一个函数这么做:

sum.invoke(2, 3) // 报错,因为只有函数类型的对象有这个自带的 invoke() 可以用

下面开始说什么是高阶函数(high-order-function)

二、高阶函数(high-order-function)

什么是高阶函数:参数或者返回值为函数类型的函数。如下就是高阶函数:

// 其中 参数 opt 为函数类型
fun f1(name :String, opt :(Int, Int) ->Int) :String {
    return name + opt.invoke(2, 3)
}

// 函数的返回值为 函数类型
private fun f2 () : (Int, Int) ->String {
    return ::sum
}
为什么要使用高阶函数?

在Java中,我想把方法作为参数传递给另一个方法,可以吗?
答案是不行的,但是Java中可以用接口来代替:

interface OnReporterListener {
   void onReporter(String msg);
}

void report(OnReporterListener listener) {
   listener.onReporter("哈哈,小猪哥");
}

但是在Kotlin中有了高阶函数的存在就可以轻松实现了

private fun report(opt : (String) -> Unit) {
    opt.invoke("哈哈,小猪哥")
}
// 同样定义一个函数前面和返回值一致的函数
private fun doReport(msg :String) {
    Log.e("xxx", "msg = $msg")
}

测试代码可以这样写:

private fun test() {
    report(::doReport)
}

是不是感觉 啊… 这 …也太麻烦了吧,得先定义一个函数,然后还得以 :: 函数名的形式传入。其实有更简单的方式,下面就来说说匿名函数lambda 表达式

三、匿名函数

匿名函数:就是指没有名字的函数,没有名字的函数要怎么调用呢?答案是没法直接调用,可以通过 把匿名函数赋值给变量或者作为函数的参数传递。

匿名函数如下:

fun(a: Int, b: Int) = a + b

上面的匿名函数是没法直接调用的,可以赋值给变量:

val f = fun(a :Int, b :Int) = a + b
f(2, 3)

注意 :匿名函数实质上也是函数类型的对象,所以可以赋值给变量。

所以上面的测试方法 report(::doReport)可以改成如下的方式:

我们直接将匿名函数传入,相当于将匿名函数直接赋值给函数类型的参数:

private fun test() {
    //  report(::doReport)
    // 这里面的 String 可以省略,因为从 report 方法中可知,传入的函数类型的入参为String
    report(fun(msg :String) {
        Log.e("xxx", "msg = $msg")
    })
}

我们把匿名函数赋值给变量 opt,那么 opt 就是一个函数类型

private fun test() {
     // report(::doReport)
     // 这里面的 String 可以省略,因为从 report 方法中可知,传入的函数类型的入参为String
    val opt = fun (msg :String) { 
        Log.e("xxx", "msg = $msg")
    }
    report(opt)
}

注意:如果匿名函数可以从左边的变量类型推断出 函数参数类型,那么匿名函数的参数类型可以省略,如下:

// 你可以以如下的方式来写,因为左边的变量指明参数类型
val noNameFun3: (Int, Int) -> Int = { a, b ->
    a + b
}

如下是不能省略的:

// 不能省略入参的参数类型,因为无法从其他地方推断入参类型
// 返回值类型时可以省略的,因为是可以推断的
val noNameFun2 = { a: Int, b: Int -> 
    a + b
}

从上面可以知道,好像真的传入了一个函数一样。而实际上你传递的是一个对象,一个函数类型的对象。因为匿名函数它本来就不是函数,而是一个函数类型的对象

这样的写法还是不够简洁,有更简洁的方式吗?当然,那就是 lambda 表达式。

四、lambda 表达式

其实Lambda表达式的本质匿名函数,而匿名函数的本质是函数类型的对象。因此,Lambda表达式、匿名函数、双冒号+函数名这三个东西,都是函数类型的对象,他们都能够赋值给变量以及当作函数的参数传递。

lambda 表达式的格式如下:
Lambda 表达式被大括号{}包围着
Lambda 表达式的参数在->的左边,如果没有参数,则只保留函数体
Lambda 表达式的函数体在->的后面
Lambda 表达式的返回值类型 为函数体最后一行代码的返回值类型

  1. 有参数有返回值的表达式 :
val lambda1 = {a :Int, b :Int ->
    a + b
}
  1. 有参数无返回值的
val lambda2 = { name : String ->
    Log.e("xx", "name = $name")
}
  1. 无参数无返回值的
val lambda3 = {
    Log.e("xx", "无参数无返回值的 lambda 表达式")
}

定义一个高阶函数如下:

fun test (name :String , opt : (a :Int , b: Int) -> Int) :String {
    return name + opt.invoke(3, 4)
}

然后我们用lambda 表达式的方式调用,如下:

test("lambda", {a, b -> a*b})

注意:由于lambda 表达式是函数test的最后一个参数,所以可以把 lambda 表达式移到外面,如下:

test("lambda") { a, b -> 
    a * b
}

如果函数只接收一个函数类型的参数,在传入Lambda表达式时,连函数调用的括号都可以去掉:

 test3 { a, b ->
     a + b
 }

fun test3 (opt : (a :Int , b: Int) -> Int) :String {
    return opt.invoke(3, 4).toString()
}

如果函数类型只有一个入参,它的这个参数也省略掉不写:

test4 { 
    val value = it // 用 it表示唯一的参数
    Log.e("xxx", "it = $it")
}

fun test4(opt : (String) -> Unit) {
    opt.invoke("123")
}

五、总结:

本文介绍了函数类型、高阶函数、匿名函数以及Lambda表达式的本质:

  1. 函数不能通过函数名直接传递,可以通过双冒号加函数名传递
  2. 双冒号+函数名、匿名函数、Lambda表达式的本质实际上都是函数类型的对象
  3. 高阶函数就只是参数列表或返回值类型存在函数类型的函数没啥特别的
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Kotlin中,高阶函数有多种用法。除了使用Lambda表达式来传递函数参数外,还可以使用匿名函数和成员引用等方式。 Lambda表达式是最基本的高阶函数用法,通过Lambda表达式可以直接在调用高阶函数时指定传入的参数。比如在调用num1AndNum2()函数时,可以通过Lambda表达式指定传入的两个整数参数进行求和。 除了Lambda表达式Kotlin还支持匿名函数的语法。匿名函数的写法更类似于常规的函数定义,可以有函数名和参数列表,而Lambda表达式则是简化的匿名函数写法。匿名函数适用于需要在函数体内使用return语句来提前返回结果的情况。 此外,Kotlin的编译器还会将高阶函数的语法转换成Java支持的语法结构。换句话说,Kotlin高阶函数在底层实现上会被转换成Java代码。具体来说,Kotlin高阶函数会被转换成接收函数对象作为参数的Java方法。 总结来说,Kotlin高阶函数用法包括Lambda表达式匿名函数和成员引用等。这些用法使得我们可以更灵活地编写代码,并将高阶函数的语法转换成Java支持的形式。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [Kotlin 高阶函数](https://blog.csdn.net/weixin_56504344/article/details/124973388)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [Kotlin高阶函数](https://blog.csdn.net/ChenYiRan123456/article/details/127983199)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值