《Kotlin核心编程》笔记:函数和Lambda表达式

news2025/1/15 16:30:48

高阶函数和lambda表达式

函数式语言⼀个典型的特征就在于函数是头等公民——我们不仅可以像类⼀样在顶层直接定义⼀个函数,也可以在⼀个函数内部定义⼀个局部函数,如下所示:

 所谓的高阶函数,你可以把它理解成“ 以其他函数作为参数或返回值的函数” 。

函数类型

在Kotlin中,函数类型的格式非常简单,举个例子:

 从中我们发现,Kotlin中的函数类型声明需遵循以下几点:

  • 通过->符号来组织参数类型和返回值类型,左边是参数类型,右边是返回值类型;
  • 必须用⼀个括号来包裹参数类型
  • 返回值类型即使是Unit,也必须显式声明
如果是一个没有参数的函数类型,参数类型部分就用()来表示。

 如果是多个参数的情况,那么我们就需要用逗号来进行分隔,如:

 此外,Kotlin还支持为声明参数指定名字,如下所示:

 支持可空类型:

 如果该函数类型的变量也是可选的话,我们还可以把整个函数类型变成可选:

 函数类型作为一个函数的返回值(高阶函数)

这表示传入⼀个类型为Int的参数,然后返回另⼀个类型为(Int)->Unit的函数。

方法和成员引用

Kotlin存在⼀种特殊的语法,通过两个冒号来实现对于某个类的方法进行引用。
以上面的代码为例,假如我们有⼀个CountryTest类的对象实例countryTest,如果要引用它的isBigEuropeanCountry方法,就可以这么写:

 此外,我们还可以直接通过这种语法,来定义⼀个类的构造方法引用变量。

其中getBook的类型为(name: String)-> Book,类似的可以引用类中的某个成员变量,如:

这对于在对Book类对象的集合应用⼀些函数式API的时候,会显得格外有用,比如:

匿名函数

 

lambda表达式

lambda是一种匿名函数,它是一种lambda语法糖

 现在用Lambda的形式来定义一个加法操作:

 Lambda语法总结:

  • ⼀个Lambda表达式必须通过{}来包裹;
  • 如果Lambda声明了参数部分的类型,且返回值类型支持类型推导,那么Lambda变量就可以省略函数类型声明;
  • 如果Lambda变量声明了函数类型,那么Lambda的参数部分的类型就可以省略。
此外,如果Lambda表达式返回的不是Unit,那么默认最后⼀行表达式的值类型就是返回值类型,如:

单个参数的隐式名称:it

 它也是Kotlin简化Lambda表达的⼀种语法糖,it是item的缩写。

Function类型

Kotlin在JVM层设计了Function类型(Function0、Function1……Function22)主要目的是用来兼容Java的Lambda表达式,其中的后缀数字代表了Lambda参数的数量,如以上的foo函数构建的其实是⼀个⽆参Lambda,所以对应的接口是Function0,如果有⼀个参数那么对应的就是Function1。它在源码中是如下定义的:

 可见每个Function类型都有一个invoke方法。

Java中,实际上并不支持把函数作为参数,而是通过函数式接口来实现这⼀特性。所以如果我们要把Java的Lambda传给Kotlin,那么它们就必须实现Kotlin的Function接口,在Kotlin中我们则不需要跟它们打交道。(当然在Java中可以使用JDK8支持的MethodHandle动态语言实现给方法传递一个函数,它的原理是基于字节码指令的封装调用)
            
神奇的数字—22
       
也许你会问⼀个问题:为什么这里Function类型最大的是Function22?如果Lambda的参数超过了22个,那该怎么办呢?
  • 22是业界的⼀种设计惯例,如Scala中也是22。
  • 在Kotlin中除了23个常用的Function类型外,还有⼀个FunctionN。在参数真的超过22个的时候,我们就可以依靠它来解决问题。更多细节可以参考https://github.com/JetBrains/kotlin/blob/master/spec-docs/function-types.md。
前面例子的代码清单中的 foo函数的返回类型是 Function0。这也意味着,如果我们调用了 foo(n),那么实质上仅仅是构造了⼀个 Function0对象。这个对象并不等价于我们要调用的过程本身。通过源码可以发现,需要调用 Function0invoke方法才能执行println方法。所以, 必须如下修改,才能够最终打印出我们 想要的结果:

在Kotlin中,我们还可以用更加简洁的方式,即使用括号调用来替代invoke,如下所示:

函数、Lambda和闭包

  • fun在没有等号、只有花括号的情况下,是我们最常见的代码块函数体,如果返回非Unit值,必须带return。

  • fun带有等号,是单表达式函数体。该情况下可以省略return。

  • 不管是用val还是fun,如果是等号加花括号的语法,那么构建的就是⼀个Lambda表达式,Lambda的参数在花括号内部声明。所以,如果左侧是fun,那么就是Lambda表达式函数体,也必须通过()或 invoke来调用Lambda,如:

在Kotlin中,你会发现匿名函数体、Lambda(以及局部函数、object表达式)在语法上都存在“ {}”,由这对花括号包裹的代码块如果访问了外部环境变量则被称为⼀个闭包。⼀个闭包可以被当作参数传递或者直接使用,它可以简单地看成“ 访问外部环境变量的函数” 。Lambda是Kotlin中最常见的闭包形式。与Java不⼀样的地方在于,Kotlin中的闭包不仅可以访问外部变量,还能够对其进行修改。

类“柯里化”风格

“柯里化”语法其实就是 函数作为返回值的一种典型应用。简单来说,柯里化指的是把接收多个参数的函数变换成⼀系列仅 接收单⼀参数函数的过程,在返回最终结果值之前,前面的函数依次 接收单个参数,然后返回下⼀个新的函数。
   
说到底,柯里化是为了简化Lambda演算理论中函数接收多参数而 出现的,它简化了理论,将多元函数变成了⼀元。
   
Lambda表达式中,还存在⼀种特殊的语 法。 如果⼀个函数只有⼀个参数,且该参数为函数类型,那么在调用 该函数时,外面的括号就可以省略 ,就像这样子:

 此外,如果参数不止⼀个,且最后⼀个参数为函数类型时,就可以采用类似柯里化风格的调用

 它等价于以下的的调用方式:

函数可变参数

Kotlin通过 varargs关键字来定义函数中的可变参数,类似Java中的 “  ”  的效果。需要注意的是, Java 中的可变参数必须是最后⼀个参 数, Kotlin 中没有这个限制 ,但两者都可以在函数体中以 数组 的方式 来使用可变参数变量。

 此外,我们可以使用 *(星号)来传入外部的变量作为可变参数的变,如下:

 

可选参数

只要为函数参数提供默认值即可实现可选参数
 
fun foo(a: Int, b: Int = 10) {

}
foo(2)
foo(2, 4)

当每个参数都有默认值时,需要指定参数名:


fun foo(a: Int = 3, b: Int = 10) {

}
foo(b = 2)
foo(a = 2, b = 4)

调用Java的函数式接口

在Android开发时,我们经常会遇到给视图绑定点击事件的场 景。以往通常的做法如下:
view.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        …
    }
})

以上的例子在Kotlin会被转化成这样:

view.setOnClickListener(object : OnClickListener {
    override fun onClick(v: View) {
        …
    }
})

Kotlin允许对Java的类库做一些优化,任何函数接收了一个Java的SAM(单一抽象方法)都可以用Kotlin的函数进行替代。以上的例子我们可以看成在Kotlin定义了以下方法:

 listener是一个函数类型的参数,它接收一个类型View的参数,然后返回Unit。我们可以用Lambda语法来简化它:

由于Kotlin存在特殊语法糖,这里的listener是setOnClickListener唯一的参数,所以我们就可以省略掉括号

带接收者的Lambda

在Kotlin中,我们可以定 义带有接收者的函数类型,如:

 此时,我们就可以用一个Int类型的变量调⽤sum方法,传入一个Int类型的参数,对其进行plus操作。

Kotlin 有一种神奇的语法 —— 类型安全构造器 ,用它可以构造类型安全的 HTML 代码,带接收者的 Lambda 语法可以很好地应用到其中。
class HTML {
    fun body() {...}
}
fun html(init: HTML.()->Unit): HTML {
    val html = HTML() // 创建了接收者对象
    html.init() // 把Lambda传递给接收者对象
    return html
}

html {
    body() // 调用接收者对象的body方法
}

withapply

Kotlin的库中还实现了两个非常好用的函数: withapply。将它 们与带接收者的 Lambda 结合,可以在某些场合进一步简化语法。这两个方法最大的作用就是可以让我们在写Lambda的时候,省 略需要多次书写的对象名,默认用 this 关键字来指向它。
   
比如,在用Android开发时,我们经常会给一些视图控件绑定属 性。以下我们利用 with 让代码可读性变得更好。
fun bindData(bean: ContentBean) {
    val titleTV = findViewById<TextView>(R.id.iv_title)
    val contentTV = findViewById<TextView>(R.id.iv_content)
    with(bean) {
        titleTV.text = this.title // this可以省略
        titleTV.textSize = this.titleFontSize
        contentTV.text = this.content
        contentTV.text = this.contentFontSize
    }
}

如果不使用with,我们就需要写好多遍bean。现在来看看with在Kotlin库中的定义:

可以看出,with函数的第1个参数为接收者类型,然后通过第2个参数创建这个类型的block方法。

因此在该接收者对象调用 block方法 时,可以在 Lambda 中直接使用 this 来代表这个对象。
我们再来看看apply函数是如何定义的:

with函数不同,apply直接被声明为类型T的一个扩展方法,它的block参数是一个返回Unit类型的函数,作为对比,withblock则可以返回自由的类型。

然而二者在很多情况下是可以互相替代的。我们 可以很容易地把上面的代码翻译成 apply 的版本:

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

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

相关文章

Debian10,docker单机安装ThingsBoard-3.3.2

1.编译项目源码 源代码编译ThingsBoard-3.3.2 2.环境准备 修改ssh登录配置 sudo vi /etc/ssh/sshd_config 打开密码登录和root用户登录3.安装docker 更新apt sudo apt update安装必备软件 sudo apt install apt-transport-https ca-certificates curl gnupg2 software-p…

SpringBoot异步方法(官方案例)

在线文档项目结构 1.源码克隆&#xff1a;git clone https://github.com/spring-guides/gs-async-method.git 2.包含两个项目initial和complete&#xff0c;initial可以根据文档练习完善&#xff0c;complete是完整项目 3.功能描述&#xff1a;构建查找服务&#xff0c;异步查询…

Linux搭建Docker版FTP,命令与Dockerfile两种方式以及容器编码解决

Linux搭建Docker版FTP&#xff0c;命令与Dockerfile两种方式以及容器编码解决一、Linux搭建Docker版FTP二、修改 Docker 容器内部 locale 系统编码最终待实现效果&#xff0c;这个方式容器内部编码并未永久改变三、编写Dockerfile方式加载带UTF-8编码环境的镜像3.1Dockerfile构…

【LINUX权限】

目录&#xff1a;前言一、shell二、权限&#xff08;一&#xff09;文件权限&#xff08;二&#xff09;权限相关指令chmod&#xff08;修改权限&#xff09;su 、sudo&#xff08;提权&#xff09;root提权chown 、chgrpumask&#xff08;掩码&#xff09;file&#xff08;三&…

性能测试工具 Lmbench 的使用方法以及解析运行结果

1. Lmbench 简介 Lmbench 是一款简易可以移植的内存测试工具&#xff0c;其主要功能有&#xff0c;带宽测评&#xff08;读取缓存文件、拷贝内存、读/写内存、管道、TCP&#xff09;&#xff0c;延时测评&#xff08;上下文切换、网络、文件系统的建立和删除、进程创建、信号处…

MYSQL中的14个实用的功能

1.group_concat 在我们平常的工作中,使用group by进行分组的场景,是非常多的。 比如想统计出用户表中,名称不同的用户的具体名称有哪些? 具体sql如下: select name from `user` group by name; 但如果想把name相同的code拼接在一起,放到另外一列中该怎么办呢? 答:…

Genesis一款基于Domino的app store应用超市

大家好&#xff0c;才是真的好。 众所周知&#xff0c;App Store是苹果iOS系统生态成功的关键因素。Domino作为一款企业应用平台&#xff0c;很多年前&#xff0c;就有人提出要创建Domino平台上的App Store。 其实比IOS早很多年前就有类似的应用管理&#xff0c;早在2000年的…

代码随想录之回溯第一课

要领&#xff1a;类型题目&#xff0c;用具体的一个例子来记忆&#xff0c;就够了&#xff0c;印象更加深刻。 一个题目&#xff0c;感觉理解不深刻&#xff0c;不透彻&#xff0c;不清晰&#xff0c;不熟练&#xff0c;就是不达标。 怎么做&#xff1f; 每天回顾复习看一次&am…

DD-1/50 12.5-50mA【接地继电器】

系列型号&#xff1a; DD-1/40接地继电器 DD-1/50接地继电器 DD-1/60接地继电器 一、 用途及工作原理 DD-1型接地继电器为瞬时动作的过电流继电器&#xff0c;用作小电流接地电力系统高电压三相交流发电机和电动机的接地零序过电流保护。继电器线圈接零序电流互感器(电缆式、…

电脑重装系统后分辨率不对应该怎么调

越来越多的小伙伴使用小白一键重装系统给自己的电脑安装上了win10系统&#xff0c;但是有部分小伙伴发现安装完win10系统之后&#xff0c;屏幕分辨率也无法进行调整&#xff0c;呈灰色状态。接下来小编就给大家详细的介绍一下小白一键重装系统分辨率不对怎么调的教程。 工具/原…

【Django】(一)基础知识

文章目录一.安装二.创建项目三.项目介绍四.APP的创建五.小案例六 模板和静态文件模板静态文件7.模板语法7.1 变量7.2列表7.3 字典7.4 过滤器7.5 if/else7.6 for标签7.7 ifequal和ifnotequal7.8注释7.9csrf_token8.模板继承8.1父模板8.2 子模板9.请求和响应10.数据库10.1安装第三…

【服务器搭建个人网站】附:接入的服务商 以及 安全评估报告该如何填写?

前言 哈喽&#xff0c;大家好&#xff0c;我是木易巷&#xff01; 关于公安备案的一些详细的细节没有分享出来&#xff0c;在公安备案期间要填写很多东西&#xff0c;其中比较难填写的就是接入的服务商和安全评估报告的填写。 这一篇给大家总结&#xff1a;公安备案接入服务商和…

⭐️【linux】关于linux-gcc,你必须要知道的知识

&#x1f332;&#x1f332;目录&#x1f332;&#x1f332; 1️⃣什么是gcc 2️⃣gcc如何生成可执行文件 3️⃣简单使用gcc 4️⃣linux的库 1️⃣什么是gcc ❄️GCC&#xff08;GNU Compiler Collection&#xff0c;GNU编译器套件&#xff09;&#xff0c;是由 GNU 开发的…

Java 中 Map 集合的基本使用

一、HashMap 1.1 HashMap 基本使用 import java.util.*;/*** author: yunhu* date: 2022/7/14*/ public class Test {public static void main(String[] args) {Map<String, Integer> map new HashMap<>();map.put("Alice", 12);map.put("Bob&qu…

62.Python 类的3要素语法

62.类的3要素语法 文章目录62.类的3要素语法1. 定义类的语法2. pass语句的作用3.我们学的5个缩进语法4.类的属性5.类的方法6. 创建对象7.类在爬虫中的应用8. 总结1. 定义类的语法 定义类的语法如下&#xff1a; class 类名: 代码块 从语法上来看&#xff1a;类的…

Edify Script (Android Shell)定制Twrp刷机包

本文将通过三方面向大家介绍Edify Script,相信你看完了这篇文章会对安卓系统可刷写插件有一个比较完整的认识,并且能过通过阅读此文章实现使用twrp刷入自己写的插件zipi包。网上能搜到的大部分是旧的,新版本去掉了很多的edify函数,照着他们那个写多半会报错。 本文分为如下…

LeetCode学习-第三十八天

第三十八天 我使用的C&#xff0c;错误的地方请见谅&#xff0c;文章初衷仅用来督促本人学习&#xff0c;如果恰巧能够给你带来帮助&#xff0c;我会十分开心。 文章目录第三十八天一、537. 复数乘法二、29. 两数相除一、537. 复数乘法 复数 可以用字符串表示&#xff0c;遵循…

CentOS7如何修改IP地址及UUID

CentOS7系统下&#xff0c;如果要把IP地址修改为192.168.1.80&#xff0c;子网掩码是255.255.255.0&#xff0c;网关是192.168.1.1&#xff0c;DNS是192.168.1.1&#xff0c;那么我们使用客户端连接CentOS或者打开系统终端&#xff0c;切换到root用户&#xff0c;命令&#xff…

开料的目的和子流程,一文读懂

经过多个月的分享&#xff0c;关于PCB行业&#xff0c;想必朋友们已经有了一些个人的理解&#xff0c;甚至对PCB行业&#xff0c;还产生了浓厚的兴趣。 但是&#xff0c;PCB生产工艺是非常复杂的&#xff0c;想要深入地学习并且学好PCB生产工艺&#xff0c;假如不在PCB的生产一…

如何从PyTorch中获取过程特征图

一、获取Tensor 神经网络在运算过程中实际上是以Tensor为格式进行计算的&#xff0c;我们只需稍稍改动一下forward函数即可从运算过程中抓到Tensor 代码如下&#xff1a; base_feature self.extractor.forward(x) #正常的前向传递 featurebase_feature.detach() …