Kotlin学习之函数
原文链接 Understanding Kotlin Functions
函数对于编程语言来说是极其重要的一个组成部分,函数可以视为是程序的执行,是真正活的代码,为啥呢?因为运行的时候你必须要执行一个函数,一般从主函数入口,开始一个套一个的函数调用。函数更能体现程序的运行。特别是近些年函数式编程的编程范式开始广泛流行,让函数的地位再次在各种语言中都得到了极大的进升。对于任何一门编程语言,如果没有学好函数,那就相当于没有学,今天就要深入的学习一下Kotlin中的函数。

首先来区分一下,什么是函数什么是方法,函数是编程语言中的一级对象,地位等同于其他Type,函数可以声明在任何地方:顶层(即在任何类任何方法的外面),类里面,另一个函数里面等。一般支持函数式编程语言更喜欢用函数。声明在类里面的函数叫作成员函数,但更准确的说是方法。比如像纯OO的编程语言Java就只会说方法(Method),而像函数式编程语言(Kotlin/Scala/Groovy)喜欢说函数。
函数的基本使用
函数(Functions)在Kotlin中的一级对象,这就意味着它能像其他类型那样,可以声明变量,可以当作参数传递,可以在函数内部定义,先从基本的使用开始。
函数的声明与定义
用关键字fun来声明函数,然后是函数名字,参数列表,返回值和函数体:修饰符 fun 函数名(参数列表) :返回类型 {函数体}
- 修饰符,对于类的成员函数才有,一般是权限open/private
- fun,用于声明这是一个函数的关键字
- 函数名,就像变量名一样,是函数的名字
- (参数列表),要用括号约束起来,就是变量的声明,多个要用逗号分隔
- :返回类型,注意冒号,也即是函数返回值的类型,如果很明显类型可以推断出来时,就可以省略
- {函数体},也即函数的真实定义部分,想要执行的一些语句
如:
fun double(x: Int): Int {return x + x
}
这就是一个标准的函数。
函数的使用
函数的使用有三种,一是调用,另一种是声明变量,再有就是当作参数(这其实是在定义一个变量,然后当作参数)。
函数的调用
函数都是表达式,都有返回值,但可以不用管返回值,调用函数的时候用括号来标识,比如前面的函数double,可以这样来调用:
val dx = double(5)
这是函数最为常用的使用方法,因为程序最终要执行,所以所有的函数最终都是要被调用的。
声明函数变量
前面说了,函数是一级类型,它可以像其他类型那样去定义变量,比如前面的double也可以这来写:
val myDouble = {x: Int -> x + x}println(myDouble(6))
这里的myDouble就是一个函数变量,它的函数类型与前面的double是一样的,它是一个变量,要想执行它的函数体要加括号。
把函数当作参数
这里会涉及高阶函数,高阶函数就是涉及函数中的函数,主要体现在函数的参数或者返回值也是一个函数。比如数组和集合的过滤(filter)和遍历(forEach)里面的参数就是一个函数:
val asc = arrayOf(1, 2, 3, 4, 5)asc.filter({(it and 0x01) == 0}).map(myDouble).forEach({ println(it) })// output-> 4, 8
因为把函数当作参数传递时都涉及函数的类型定义,而一般情况下用lambda是最方便的,先有个印象,后面会详细讲解。
参数
函数的参数还有两种比较有用的变体,称之为命名参数和默认值,这两个通常会一起使用。
命名参数
当一个函数的参数比较多时,那么在调用时想要传递参数就比较蛋疼,特别是还有相同类型的参数的时候,一片混乱,比如:
fun log(tag: String, event: String, source: String, amount: Int, price: Float, persist: Boolean): Unit {println("$tag, $event, $amount $price")if (persist) {// write to file}
}log("func", "Function arguments", "Hard way", 5, 2.3f, false)
这样调用,参数太多了,并且相同类型的有三个,这三个极容易传错,而且因为类型检查 不会报错,可能会引发极难调试的bug。
这时就可以使用命名参数来缓解了,命名参数,就是在调用函数,传递参数的时候,指定参数的名字,即就是在声明函数时参数的名字,用以指定具体参数,然后这时就可不用管参数的相对顺序了,比如上面的函数也可以这样调用:
log(event = "Named arguments",tag = "func",source = "Elegant",amount = 5,persist = false,price = 100f)
不会出错,而且可读性大大加强。但需要注意的是,如果要使用命名参数,就要保持一致性,给所有的参数都要命名。所以,当参数比较多的时候还是比较蛋疼,这时就需要用到参数默认值了。
参数默认值
默认值也即是在声明参数的时候指定一个默认值,在调用的时候可以省略这个参数了,比如:
fun foo(x: Int, y: Int = 0): Int {return x + y
}fun afoo(x: Int = 0, y: Int): Int {return x - y
}foo(3)
afoo(y = 5)
注意,如果默认参数是最后一个参数,那么可以直接省略它,如示例中的foo(3),但如果默认参数不是最后一个,想省略的话,必须要用命名参数,如afoo(y = 5)。当然了,两个参数都传也可以的:
foo(3, 5)
afoo(3, 5)
所以要把默认值和命名参数结合起来才能发挥最大的价值:
fun log(tag: String, event: String, source: String = "Elegant", amount: Int = 0, price: Float, persist: Boolean = false): Unit {println("$tag, $event, $amount $price")if (persist) {// write to file}
}log(event = "Named arguments",tag = "func",price = 100f)
把握一下使用原则:如果参数不多(4个以内),那么就把默认参数往后放,调用的时候也可以不用命名参数,直接省略默认参数就好;如果参数比较多,也要把默认参数往后放,在调用的时候尽可能的使用命名参数。
返回值
函数的返回值是在参数列表之后,函数体之前用冒号加类型来声明。
fun printHello(name: String?): Unit {if (name != null)println("Hello $name")elseprintln("Hi there!")// `return Unit` or `return` is optional
}
如果函数没有返回值就用Unit来声明,相当于Java中的void,但更多的时候是可以省略的:
fun printHello(name: String?) { ... }
当函数体只有一个表达式的时候,这个时候可以省略掉函数体,而把表达式直接写在函数声明的后面,用**赋值符=**来连接,如前面的double也可以这样写:
fun double(x: Int): Int = x + x
这个时候,因为函数体只有一个表达式,所以返回类型很容易推断出来,意味着这时返回类型的声明也可以省略掉:
fun double(x: Int) = x + x
这会让代码非常的简洁,又不失可读性。
解构返回
Kotlin的函数只能有一个返回值,代表某一个类型的一个变量,如果想有多个返回值,就需要用复杂的类型,比如同一类型的多个有规律的变量可能就要用集合,如数组列表等。但如果类型不同,但逻辑上有关系的2个到3个值,如果想要一起返回,就需要用到组合类型如Pair和Triple,Pair可以把两个不同类型的变量组合成一个对象,Triple可以把三个不同类型的变量组合成一个对象,这样就可以在函数中返回了。
fun nameAge() = Pair("Alex", 50)fun fullName() = Triple("Donald", "Jonh", "Trump")
对于函数的调用者也很麻烦,要先声明Pair或者Triple对象,然后再拆解,比如这样:
val pna = nameAge()
println("Name ${pna.first}, age ${pna.second}")
这显然比较笨拙,不够简洁。在Kotlin中有更好的做法,可以在函数调用的时候,对返回值进行拆解,称之为解构,如下写法与上面是一样的:
val (name, age) = nameAge()
println("Name $name, age $age")
而且,如果只对组合中的某几个感兴趣,可以把不想要的变量用下划线_(underscore)来表示,比如说:
val (firstName, _, lastName) = fullName()
println("This is $firstName $lastName")
尾部lambda参数传递
前面说了函数可以作为参数传递给其他函数,但我们在使用的时候,一般会直接把一个lambda传递进去,比如说:
fun execute(a: Int, f: (Int)->Int): Int {if (a < 0) {return -1}return f(a)
}
调用的时候,可以这样:
execute(3, { it * it })
但更建议的方式是把lambda放到函数调用之外:
execute(5) { it + it }
再比如像集合的函数式写法,通常也只传递一个lambda,这时一般都写在函数调用之外,并且当目标函数没有其他参数时也即除了要传入的lambda外无其他参数时,代表函数调用的括号也可以省略:
val nums = arrayOf(1, 2, 3, 4, 5)
nums.filter { it and 0x01 == 0 } // 等同于filter({ it and 0x01 == 0 }).map { it * it } // 等同于map({ it * it }).forEach { println(it) } // 等同于forEach({ println(it) })
这样写非常的简洁,但会牺牲一些可读性,因为花样多了,就会比较难识别出来函数的声明与函数的调用,甚至有时候会分不清函数与普通的变量。所以,识别函数调用有两种方式,一是看有没有括号,另外就看有没有尾部lambda。
匿名函数
匿名函数就是不指定函数的名字,但保留关键字fun,通常用于把函数当作 参数传递给高阶函数时使用。如:
list.forEach(fun (item) { println(item) })
但,因为对lambda支持的非常好,所以凡事用到匿名函数的地言都可以用lambda来替换(当然了,lambda也是匿名函数的一个实现方式)。匿名函数的一个用处就 可以加代码标签(label),以方便终止语句的精准控制,可以参见另一篇文章中关于局部控制的讨论,可能也只有这个地方适用匿名函数,其他情况都应该直接用lambda。
内部函数
就是定义在另外一个函数内部的函数,通常用作于传递参数或者返回值,如:
fun foo(): ()->Unit {fun bar() {println("Func bar inside fun foo")}return bar
}
事实上,随处可见的lambda都可以算上是内部函数。
高阶函数
高阶函数就是函数的函数,也就是说函数的参数或者返回值是一个函数的函数,也即把函数像其他类型那样使用。函数在Kotlin中一级类型(first class type),因此从语义层面支持了函数式编程范式,当然也就支持了高阶函数以及lambdas。比如像集合的操作filter/map/fold都是高阶函数,因为它们接受一个函数作为参数。
函数类型
高阶函数是把函数作为参数或者返回值,但显然并不是所有的函数都能当作高阶函数的参数或者返回值,换句话说,函数本身其实也是有类型之别的,两个函数不见得就是一样的。函数是用来针对其参数,然后在函数体内进行一些运算最终返回一个值,所以区分不同的函数最关键的是输入参数和返回值,与其名字其实没有关系,因此输入参数一致,返回值一致就可以视为同一种函数。
函数的类型用参数和返回值来表示,如**(A, B) -> R**形式,A和B是参数,R是返回值,需要注意的是括号不能省略,常见的具体形式有:
- () -> Unit 无参数无返回值
- () -> R 无参数有返回值
- (A) -> Unit 有一个参数,无返回值
- (A) -> R 一个参数,一个返回值
- (A, B) -> Unit 两个参数,无返回值
- (A, B) -> R 两个参数,一个返回值
函数的类型与方法签名类似(method signature),代表着某一类的函数。在高阶函数的函数参数或者返回函数就需要用函数类型来声明。
实例化一个函数类型
有很多种途径可以实例化一个函数类型,比较常见的有:
- 通过lambda表达式,如{ a, b -> a + b },这就是一个函数类型(A, B) -> R的实例
- 匿名函数,如fun(a: Int, b: Int): Int { return if (a > 0 && b > 0) a + b else -1 }
- 引用现存的某一个函数,函数签名(参数相同,返回值相同)就视为同一种函数类型,那么已定义好的函数中有能匹配的就可以直接引用过来,**顶级函数和构造函数用:😗*来引用,**类成员函数用类名:😗*来引用,如::isOdd, String::toInt, ::Tripple
lambda表达式
就是匿名隐式函数体,匿名是不用指定函数的名字,连参数的类型和返回值的类型也都省略,有时甚至连参数都可以省略,只有一个函数体,是最为简洁的一种函数定义方式,通常用于传递给高阶函数的参数,lambda力求简洁,所以但凡能推断出来的都可以省略。最简洁的lambda只有函数体,如val asc = IntArray(5) { it * it } // 创建一个长度为5的整数数组并初始化为[0,1,4,9,16]。

lambda的形式是**{ A, B -> expressions }**,外面的花括号不可省略,这是lambda的标识,然后是参数列表,->用于分隔参数和函数体,除了函数体,其余的都可以省略掉,只要能推断出来。
Trailing lambdas(尾部lambda)
这个前面讲过了,再复习一下,当一个函数的最后一个参数是一个函数时,就可以在函数的调用外部写lambda,比如:
val product = items.fold(1) { acc, e -> acc * e }run { println("...") }
隐式参数
如果lambda表达式只有一个参数,那么这个参数也可以省略,只写函数体就可以,并且可以用隐式参数it,比如:
ints.filter { it > 0 } // this literal is of type '(it: Int) -> Boolean'val asc = IntArray(5) { it * it }
lambda的返回值
lambda力求简洁,所以函数体的最后一个表达式的值即是此lambda的返回值,一般不用显式的return:
ints.filter { it > 0 } // boolean result of 'it > 0' is returnedval asc = IntArray(5) { it * it } // it * it is the returnints.filter {val shouldFilter = it > 0shouldFilter
}
如果要用显式的return语句,要注意scope,在这篇文章有深入讨论,用隐式label来限定scope:
ints.filter {val shouldFilter = it > 0return@filter shouldFilter
}
丢弃参数
有时候,参数有多个,但可能并不会全都使用,仅使用了其中一个,这时不使用的参数就可以用**下划线_(underscore)**来代替,以表示这个参数不会被使用:
map.forEach { (_, value) -> println("$value!") }
Kotlin的lambda可以写出非常简洁的函数式链式语句,一气呵成可读性又非常的好,比如:
val headers = fetchHeaders()
headers.filter { it.length == 5 }.sortedBy { it }.map { it.uppercase() }.forEach { println(it) }
未完待续
函数是Kotlin中最最重要的一个类型,博大精深,用法多样,但万变不离其宗,重点理解函数就是行为,是可以运行的一组语句和表达式的小集合;区分开函数的声明和定义就可以了。
Kotlin的函数类型很多,这里只介绍了一些基础,后面将会继续介绍一些高级的函数如内联函数(inline functions),infix notation以及Extensions和Scope functions。
参考资料
- Kotlin functions
- Kotlin | Default and Named argument
- Kotlin——高级篇(一):Lambda表达式详解
- Lambda Expressions in Kotlin
- Kotlin lambda表达式
原创不易,打赏,点赞,在看,收藏,分享 总要有一个吧
相关文章:
Kotlin学习之函数
原文链接 Understanding Kotlin Functions 函数对于编程语言来说是极其重要的一个组成部分,函数可以视为是程序的执行,是真正活的代码,为啥呢?因为运行的时候你必须要执行一个函数,一般从主函数入口,开始一…...
若依启动步骤
1.创建数据库 2.启动redis 3.改后端的数据库连接配置 4.配置redis redis的地址:cmd中ipconfig命令查看 6.启动后端:如下 7.启动前端ruoyi-ui中 先运行npm install,再npm run dev。项目就启动成功了。 用户名:admin 密码&#x…...
qt-C++笔记之两个窗口ui的交互
qt-C笔记之两个窗口ui的交互 code review! 文章目录 qt-C笔记之两个窗口ui的交互0.运行1.文件结构2.先创建widget项目,搞一个窗口ui出来3.项目添加第二个widget窗口出来4.补充代码4.1.qt_widget_interaction.pro4.2.main.cpp4.3.widget.h4.4.widget.cpp4.5.second…...
Redis-核心数据结构
五种数据结构 String结构 String结构应用场景 Hash结构 Hash结构应用场景 List结构 List结构应用场景 Set结构 Set结构应用场景 ZSet有序结构 ZSet有序结构应用场景...
设计模式—结构型模式之外观模式(门面模式)
设计模式—结构型模式之外观模式(门面模式) 外观(Facade)模式又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。 例子 我们的电脑会有很多 组件&am…...
CentOS Stream 9-使用 systemd 管理自己程序时自定义日志路径
systemd 文件 [rootnode1 ~]# cat /etc/systemd/system/spms-wvp.service [Unit] DescriptionWVP service [Service] # 关键配置部分,注意这里的 spms-wvp ,后面需要用 SyslogIdentifierspms-wvp StandardOutputsyslog StandardErrorsyslog Typesimple Environment…...
动态页面调研及设计方案
文章目录 vue2 动态表单、动态页面调研一、form-generator二、ng-form-element三、Variant Form四、form-create vue2 动态表单、动态页面调研 一、form-generator 预览:https://mrhj.gitee.io/form-generator/#/ Vue2 Element UI支持拖拽生成表单不支持其他组件…...
鸿蒙4.0开发笔记之DevEco Studio之配置代码片段快速生成(三)
一、作用 配置代码片段可以让我们在Deveco Studio中进行开发时快速调取常用的代码块、字符串或者某段具有特殊含义的文字。其实现方式类似于调用定义好变量,然而这个变量是存在于Deveco Studio中的,并不会占用项目的资源。 二、配置代码段的方法 1、打…...
HarmonyOS真机调试报错:INSTALL_PARSE_FAILED_USESDK_ERROR处理
文章目录 1、 新建应用时选择与自己真机匹配的sdk版本2、 根据报错提示连接打开处理方案3、查询真机版本对应的**compileSdkVersion** 和 **compatibleSdkVersion** 提示3.1版本之后和3.1版本之前的不同命令(此处为3.0版本)4、根据查询修改参数5、连接成…...
webSocket基于面向对象二次封装
今天不睡,熬夜赶了个WebSocket 二次封装,也对这几天文章摸鱼感到抱歉,所以我出了一个注释非常非常全的代码 思路如下 首先,需要通过调用connect方法来建立WebSocket连接。当连接成功时,会调用我提供的回调函数,并将连接成功的消息帧作为参数…...
【Web】PHP反序列化的一些trick
目录 ①__wakeup绕过 ②加号绕过正则匹配 ③引用绕过相等 ④16进制绕过关键词过滤 ⑤Exception绕过 ⑥字符串逃逸 要中期考试乐(悲) ①__wakeup绕过 反序列化字符串中表示属性数量的值 大于 大括号内实际属性的数量时,wakeup方法会被绕过 (php5-p…...
【测试功能篇 01】Jmeter 压测接口最大并发量、吞吐量、TPS
压力测试,我们针对比较关键的接口,可以进行相应的压力测试,主要还是测试看看接口能抗住多少的请求数,TPS稳定在多少,也就是吞吐量多少 安装 Jmeter的安装很简单,官网下载地址 http://jmeter.apache.org/ &…...
代码随想录算法训练营第四十九天| 123.买卖股票的最佳时机III 188.买卖股票的最佳时机IV
文档讲解:代码随想录 视频讲解:代码随想录B站账号 状态:看了视频题解和文章解析后做出来了 123.买卖股票的最佳时机III class Solution:def maxProfit(self, prices: List[int]) -> int:if len(prices) 0:return 0dp [[0] * 5 for _ in…...
11.20 知识总结(choices参数、MVC和MTV的模式、Django与Ajax技术)
一、 choices参数的使用 1.1 作用 针对某个可以列举完全的可能性字段,我们应该如何存储 .只要某个字段的可能性是可以列举完全的,那么一般情况下都会采用choices参数 1.2 应用场景 应用场景: 学历: 小学 初中 高中 本科 硕士…...
深度学习二维码识别 计算机竞赛
文章目录 0 前言2 二维码基础概念2.1 二维码介绍2.2 QRCode2.3 QRCode 特点 3 机器视觉二维码识别技术3.1 二维码的识别流程3.2 二维码定位3.3 常用的扫描方法 4 深度学习二维码识别4.1 部分关键代码 5 测试结果6 最后 0 前言 🔥 优质竞赛项目系列,今天…...
C#关于TimeSpan结构的使用和获取两个时间差
C#中的TimeSpan结构可以获取两个时间的时间差。 它主要具有以下属性和方法: 属性: Days:获取时间间隔的天数部分。Hours:获取时间间隔的小时数部分(不包括整天的小时数)。Minutes:获取时间间…...
Git分支管理
愿所有美好如期而遇 目录 理解分支 创建分支 切换分支 合并分支 删除分支 合并冲突 分支管理策略 理解分支 每次提交master都会前进一步,随着不断提交,master分支的线越来越长,而HEAD指向哪条分支就是当前工作的分支。 master分支是我…...
《视觉SLAM十四讲》-- 建图
11 建图 11.1 概述 (1)地图的几类用处: 定位:导航:机器人在地图中进行路径规划;避障重建交互:人与地图之间的互动 (2)几类地图 稀疏地图稠密地图语义地图 11.2 单目…...
智能配电箱柜管理系统
智能配电箱柜管理系统是一个综合性的管理系统,专门设计用于监控和控制智能配电箱和柜的运行。这个系统集成了先进的技术和智能化功能,以确保配电系统的正常运行并提高其效率。依托电易云-智慧电力物联网,以下是智能配电箱柜管理系统的主要特点…...
聊聊近些年 CPU 在微架构、IO 速率上的演进过程
大家好,我是飞哥! 在上一篇《深入了解 CPU 的型号、代际架构与微架构》 中我们介绍了我手头的一颗 Intel(R) Core(TM) i5 的型号规则,以及它的物理硬件的 Die 图结构。以及它对应的 Skylake 核的微架构实现。 不少同学开始问我其它型号的 CPU…...
工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...
LRU 缓存机制详解与实现(Java版) + 力扣解决
📌 LRU 缓存机制详解与实现(Java版) 一、📖 问题背景 在日常开发中,我们经常会使用 缓存(Cache) 来提升性能。但由于内存有限,缓存不可能无限增长,于是需要策略决定&am…...
比较数据迁移后MySQL数据库和OceanBase数据仓库中的表
设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...
MFE(微前端) Module Federation:Webpack.config.js文件中每个属性的含义解释
以Module Federation 插件详为例,Webpack.config.js它可能的配置和含义如下: 前言 Module Federation 的Webpack.config.js核心配置包括: name filename(定义应用标识) remotes(引用远程模块࿰…...
论文阅读:Matting by Generation
今天介绍一篇关于 matting 抠图的文章,抠图也算是计算机视觉里面非常经典的一个任务了。从早期的经典算法到如今的深度学习算法,已经有很多的工作和这个任务相关。这两年 diffusion 模型很火,大家又开始用 diffusion 模型做各种 CV 任务了&am…...
