Kotlin小知识之高阶函数

news2024/11/27 2:43:23

文章目录

  • 高阶函数
    • 定义高阶函数
      • 函数类型
      • 高阶函数示例
    • 内联函数
      • 内联函数的作用
      • 内联函数的用法
    • noinline与crossinline

高阶函数

定义高阶函数

  • 高阶函数和Lambda的关系是密不可分的.
  • 像接受Lambda参数的函数就可以称为具有函数式编程风格的API了
  • 当我们想要定义自己的函数式API那就得借助高阶函数来进行实现了.
  • 高阶函数的定义:如果一个函数是另外一个函数,或者一个函数的返回值是另外一个函数,那么就称这个函数为高阶函数.

函数类型

  • 在编程语言当中由整形,布尔类型等字段类型,而Kotlin又增加了一个函数类型的概念

  • 如果我们将这个函数类型添加到一个函数的参数声明或者返回值声明当中,那么这就是高阶函数

  • 定义一个函数类型,不同于定义一个普通的字段类型,函数类型的语法规则如下:

  • (String, Int) -> Unit

  • 既然是定义一个函数类型,那么最关键的就是要声明该函数的参数以及它的返回值是什么.

  • 因此,->左边部分就是用来声明该函数接收什么参数,多个参数之间使用逗号进行隔开,如果不接受任何参使用一对空括号即可.

  • 如果没有返回值就使用Unit,它大致相当于java当中的void

  • 现在将上述函数类型添加到一个函数的参数当中,那这个函数就是一个高阶函数

fun example(func: (String, Int) -> Unit) {
   func("hello", 123) 
}
  • 在example()函数当中接收了一个函数类型的参数,因此example()函数就是一个高阶函数
  • 而调用一个函数类型的参数,语法类似与调用一个普通的函数,只需要在参数名后面加上一对括号,并在括号当中传入必要的参数
  • 高阶函数允许让函数类型的参数来决定函数的执行逻辑,即使是同一个高阶函数,只要出传入不同的函数类型参数,那么它的执行逻辑和最终返回结果就可能是完全不同的

高阶函数示例

  • 新建一个HigherOrderFunction.kt文件
  • 在其中编写一个高阶函数
fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int) : Int {
    val result = operation(num1, num2)
    return result
} 
  • 高阶函数定义好了,该高阶函数接收了一个函数类型的参数,因此我们要去编写所对应的函数类型相匹配的函数即可.
fun plus(num1: Int, num2: Int) : Int {
    return num1 + num2
}

fun minus(num1: Int, num2: Int) : Int {
    return num1 - num2
}
  • 这里定义了两个函数,都是和高阶函数中函数类型的参数相匹配的,但是这两个函数却实现的是不同的功能,第一个是俩数之和,第二个是俩数之差.
  • 有了上述的函数之后,我们就可以调用num1AndNum2()函数了,在main函数当中编写如下代码
fun main() {
    val num1 = 5
    val num2 = 1
    val res1 = num1AndNum2(num1, num2, ::plus)
    val res2 = num1AndNum2(num1, num2, ::minus)
    println("res1 is $res1")
    println("res2 is $res2")
}
  • 这里需要注意的就是调用num1AndNum2()函数的方式,第三个参数使用使用了::plus和::minus的写法,这是一种函数引用的写法,表示将plus()和minus()函数作为参数传递给num1AndNum2()函数
  • 其实我们还可以不编写plus()函数和minus()函数,而是使用Lamdba表达式的方式来调用高阶函数
fun main() {
    val num1 = 5
    val num2 = 1
    val res1 = num1AndNum2(num1, num2) { num1, num2 ->
        num1 + num2
    }
    val res2 = num1AndNum2(num1, num2) { num1, num2 ->
        num1 - num2
    }
    println("res1 is $res1")
    println("res2 is $res2")
}
  • 使用高阶函数模拟一个apply函数的类似功能
  • 给StringBuilder类定义一个扩展函数builder(),然后这个扩展函数builder又接收了一个函数类型的参数,这个函数类型的参数它的参数为空,返回值也为空,但是builder扩展函数的返回值为StringBuild
fun StringBuilder.builder(block: StringBuilder.() -> Unit) : StringBuilder {
    block()
    return this
}
  • 需要注意的就是这个函数类型的声明方式和之前的示例有所不同,它在函数类型参数前面加上了StringBuilder.,这个意思就是在函数类型之前加上ClassName.表示该函数类型的定义在哪个类当中,这个才是定义高阶函数完整的语法规则.
  • 现在我们就可以使用builder函数来代替apply函数完成一些操作
fun main() {
    val list = listOf("Apple", "Banana", "Orange")
    val res = StringBuilder().builder {
        append("Start eating fruits.\n")
        for (f in list) {
            append(f).append("\n")
        }
        append("Ate all fruits.")
    }
    println(res)
}

内联函数

内联函数的作用

  • 要分析内联函数的作用,得先知道高阶函数的原理
  • 我们首先要知道Kotlin中的代码最后还是要转换称为Java字节码的
  • 但是Java当中又没有对应的高阶函数的概念,其实这一切都要归功于Kotlin的编译器,Kotlin的编译器会将高阶函数的代码转换称为Java当中的语法结构.
  • 转换称为Java语法结构之后,函数类型的参数会被转换成为一个接口,这个接口是Kotlin内置的一个接口,在这个接口当中有一个带实现的invoke()方法
  • 所以最后实际上调用函数类型参数就是调用了接口的invoke()函数,并将相应的参数传递进去即可.
  • 然后之前函数类型参数的Lambda表达式就会改写成为了接口的匿名类实现
  • 原来我们一直使用的Lambda表达式会在底层被转换成为匿名类的实现方式
  • 这就表明我们每调用一次Lambda表达式,都会创建一个新的匿名类实例,这也会造成额外的内存和性能开销
  • 为了解决这个问题,Kotlin提供了内联函数的功能,它可以将Lambda表达式带来的内存和性能开销问题完全消除.

内联函数的用法

  • 内联函数的用法非常简单只需要在定义高阶函数时加上inline关键字的声明即可
inline fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
    val result = operation(num1, num2)
    return result
}
  • 那么内联函数的工作原理其实就是:Kotlin编译器会将内联函数中的代码在编译的时候自动替换到调用它的地方,这样也就不存在运行时的开销了.
  • 大致过程就是:Kotlin编译器会将Lambda表达式中的代码替换到函数类型参数调用的地方

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tP1DAKhD-1669819326250)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AE%89%E5%8D%93/image-20221130161744402.png)]

  • 然后再将内联函数中的代码替换到函数调用的地方

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f6GbyL04-1669819326252)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AE%89%E5%8D%93/image-20221130161959159.png)]

  • 最终的代码就成了这个样子

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gNnGhJSK-1669819326253)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AE%89%E5%8D%93/image-20221130162027310.png)]

  • 这样内联函数就可以完全消除Lambda表达式带来的内存和性能开销问题

noinline与crossinline

  • 一个比较特殊的情况.比如,一个高阶函数中如果接收了两个或者更多函数类型的参数,这时我们给函数加上了inline关键字,那么Kotlin编译器会自动将所有引用的Lambda表达式全部进行替换.
  • 但是我们现在只想给其中一个Lambad表达式该怎么办,那么就可以使用noline关键字,如下所示:
inline fun inlineTest(block1: () -> Unit, noinline block2: () -> Unit) {
    
}
  • 为什么Kotlin要提供一个noinline关键字来排除内联功能?因为内联函数类型参数在编译的时候会被进行代码替换,因此他没有真正的参数属性,非内联的函数类型参数可以自由地传递给其他任何函数,因为它就是一个真实参数,而内联函数类型参数只允许传递给另外一个内联函数,这就是它最大的局限性.
  • 另外内敛函数和非内联函数还有一个重要的区别,那就是内联函数所引用的Lambda表达式中时可以使用return关键字进行函数返回.
  • 将高阶函数声明称为内联函数是一种良好的编程习惯,事实上大多数的高阶函数都可以直接声明成为内联函数,但是也是有少部分情况是例外的:
inline fun runRunnable(block: () -> Unit) {
    val ruunable = Runnable {
        block()
    }
    runnable.run()
}
  • 这段代码在没有加上inline关键字的时候肯定是没有问题,但是将这个高阶函数声明成内联函数就会出现下面的问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IndZBaOg-1669819326255)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AE%89%E5%8D%93/image-20221130222656459.png)]

  • 上述代码出现问题的主要原因是:在runRunnable()函数中,我们创建了一个Runnable对象,并在Runnable的Lambda表达式中调用了传入的函数类型参数.
  • 而Lambda表达式在编译的时候会转换成为匿名类的实现方式,也就是说,上述代码实际上是在匿名类中调用了传入的函数类型参数.
  • 而内联函数所引用的Lambda表达式允许使用return关键字进行函数返回,但是由于我们是在匿名内部类中调用的函数类型的参数,此时是不可能进行外层调用函数返回的,最多只能对匿名类中的调用函数进行返回
  • 也就是说我们在高阶函数当中创建了另外Lambda或者匿名类的实现,并且在这些实现中调用函数类型参数,此时再将高阶函数声明成内联函数,就一定会提示错误.
  • 所以在这个时候,我们如果还想要使用内联函数的时候,就需要借助crossinline关键字就可以很好的解决问题.
inline fun runRunnable(crossinline block: () -> Unit) {
    val ruunable = Runnable {
        block()
    }
    runnable.run()
}
  • 这个关键字其实就相当于一个契约一样,它用于保证在内敛函数的Lambda表达式一定不会使用return关键字,这样冲突就不存在了.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/50018.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用 Learner Lab - 使用 AWS Lambda 将图片写入 S3

使用 Learner Lab - 使用 AWS Lambda 将图片写入 S3 AWS Academy Learner Lab 是提供一个帐号让学生可以自行使用 AWS 的服务,让学生可以在 100 USD的金额下,自行练习所要使用的 AWS 服务,以下使用 AWS Lambda 将图片写入 S3。 如何进入 Le…

Git源码(Linus 2005 年提交的最初版本)阅读笔记

Linus 发疯文学欣赏 Git 是 Linux 之父 Linus Torvalds 于2005年开发的用于帮助管理 Linux 内核开发的开源版本控制软件。 美好的一天从阅读 Linus 的发疯文学开始。 (1) Linus 教你学习 Git (2) Linus 评价 CVS (Concurrent Version System) (3) 独一无二的 Linus 调侃结束&a…

手把手教你写一个生成yapi接口代码Chrome 扩展插件

前言 公司想开发个公众号,想在公众号里做业务,也不是做小程序,但是以后也可能做小程序。emm,就是这么随意。所以就找个到了uniapp,说是可以开发一套代码,多平台运行。开发语法还是vue,感觉也没…

RabbitMQ消息队列——快速入门

目录 1、MQ介绍 1.1、什么是MQ? 1.2、MQ的能够解决的问题 1.2.1、削峰填谷 1.2.3、异步处理 1.3、MQ的选择 1.3.1、Kafka 1.3.2、ActiveMQ 1.3.3、RocketMQ 1.3.4、RabbitMQ 2、RabbitMQ的介绍 2.1、RabbitMQ的概述 2.2、AMQP 2.3、JMS 2.4、RabbitMQ…

关系抽取:传统:UniRel: Unified Representation and Interaction for Joint Relational

针对传统下的三元组抽取提出的一种方法,在NYT和webNLG数据集上,再次刷新榜单。 本来对这个结果不是很确定,但作者公布了源码,we can reformulate it . 很少在看到这种文章了吧。 Core idea 换了一种解释思路。 从entity-entity的…

ARM汇编之乘法指令

ARM汇编之乘法指令前言 首先,请问大家几个小小问题,你清楚: 乘法指令有哪些种类呢?ARM乘法指令具体的使用场景又有哪些? 今天,我们来一起探索并回答这些问题。为了便于大家理解,以下是本文的…

精准诊断,精确治疗,智芯传感ZXPA侵入式压力传感器为心血管疾病患者带来福音

近日,据联合国《世界人口展望2022》报告显示,地球人口已正式步入“80亿时代”!人口数量增加,从一个侧面反映了人类文明的进步。此外,随着人类预期寿命增加,加上生育率下降,将加剧全球人口老龄化…

基于模型的聚类和R语言中的高斯混合模型

介绍 四种最常见的聚类方法模型是层次聚类,k均值聚类,基于模型的聚类和基于密度的聚类 . 最近我们被客户要求撰写关于聚类的研究报告,包括一些图形和统计输出。 可以基于两个主要目标评估良好的聚类算法: 高组内相似性低组间相…

小程序中的自定义组件以及组件通信、数据共享、插槽、behaviors

一、创建组件和使用自定义组件 1.创建组件 ①在项目的根目录中,鼠标右键,创建components -> 文件夹 ②在新建的components -> 文件夹上,鼠标右键,点击“新建Component ③键入组件的名称之后回车,会自动生成组件对应的4个…

卡尔曼滤波Kalman Filtering:介绍

本文是Quantitative Methods and Analysis: Pairs Trading此书的读书笔记。 控制理论(control theory)是工程学的分支之一,主要应对工程系统控制的问题。比如控制汽车发动机的功率输出,稳定电动机的转速,控制“反应速率”&#x…

企业数字化办公利器——华为云桌面Workspace

随着云办公生态的逐渐成熟,华为云桌面也成为了越来越多企业实现随时随地移动办公的选择。 华为云桌面Workspace是一款SAAS产品,是基于华为云云原生架构设计和构建的云桌面服务,可支持云桌面的快速创建、部署和集中运维管理,免除大…

2023年java代做题目参考整理

为方便毕业设计选题,特别整理以下几百题目供参考 班级风采网站的设计 工资绩效管理系统的开发 电子产品销售网站的设计与实现 酒店预订信息管理系统的设计 成绩管理系统 B2C的电子商务系统(J2EE) B2C购物网站设计 教学网站及网上考试系统的设计与实现 ERP采…

STM32学习之Keil5软件配置

前言:代码编写环境可以让编写者在代码编写上有一定的好处,从而得到高效的代码编写。本次笔者写的是一些市面上常用的嵌入式开发软件Keil5,在初始化使用软件界面需要进行配置的。主要分为五大部分(文本美化、代码编辑技巧、查找和替…

一篇图解Linux内存碎片整理

我们知道物理内存是以页为单位进行管理的,每个内存页大小默认是4K(大页除外)。申请物理内存时,一般都是按顺序分配的,但释放内存的行为是随机的。随着系统运行时间变长后,将会出现以下情况: 要解…

树莓派板载蓝牙使用

1 设置树莓派板载蓝牙 1.1 相关环境安装、配置 sudo apt-get update sudo apt-get install pi-bluetooth bluez bluez-firmware blueman1.2 树莓派蓝牙操作 参考: https://blog.csdn.net/guzhong10/article/details/78574577 有时候会失败, 可以尝试…

[附源码]SSM计算机毕业设计学校缴费系统JAVA

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…

文件包含总结

概念 是指将已有的代码以文件形式包含到某个指定的代码中,从而使用其中的代码或者数据,一般是为了方便直接调用所需文件,文件包含的存在使得开发变得更加灵活和方便。 文件包含常见函数 include() // 执行到include时才包含文件&#xff…

区间信息维护与查询【线段树 】 - 原理2 线段树中的“懒操作”

区间信息维护与查询【线段树 】 - 原理2 线段树中的“懒操作” 之前我们已经说了对线段树的点更新和区间查询,若要求对区间中的所有点都进行更新,该怎么办? 若对区间的每个点都进行更新,则时间复杂度较高,可以引入懒…

Cocos2d-x 3D渲染技术 (三)

包围盒算法 说白了就是给物体装进一个盒子里,该盒子可以装下物体。目的是为了进行碰撞检测。 种类: 球状碰撞体立方体碰撞体胶囊碰撞体Mesh碰撞体 实现原理是OBB包围盒。 经常使用的两种碰撞算法是OBB包围盒和AABB包围盒算法。 OBB包围盒算法 方向…

JavaScript -- 01. 基础语法介绍

文章目录基础语法1 Hello World2 JS的编写位置3 基本语法3.1 多行注释3.2 单行注释3.3 区分大小写3.4 空格和换行会被忽略3.5 以分号结尾3.6 字面量3.7 变量3.8 变量的内存结构3.9 常量3.10 标识符基础语法 JS的基本语法 1 Hello World js的三种输出方式 <!DOCTYPE html&g…