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…...

Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...

并发编程 - go版
1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...

uniapp 开发ios, xcode 提交app store connect 和 testflight内测
uniapp 中配置 配置manifest 文档:manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号:4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...
Bean 作用域有哪些?如何答出技术深度?
导语: Spring 面试绕不开 Bean 的作用域问题,这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开,结合典型面试题及实战场景,帮你厘清重点,打破模板式回答,…...

MyBatis中关于缓存的理解
MyBatis缓存 MyBatis系统当中默认定义两级缓存:一级缓存、二级缓存 默认情况下,只有一级缓存开启(sqlSession级别的缓存)二级缓存需要手动开启配置,需要局域namespace级别的缓存 一级缓存(本地缓存&#…...

stm32wle5 lpuart DMA数据不接收
配置波特率9600时,需要使用外部低速晶振...
微服务通信安全:深入解析mTLS的原理与实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、引言:微服务时代的通信安全挑战 随着云原生和微服务架构的普及,服务间的通信安全成为系统设计的核心议题。传统的单体架构中&…...

Python训练营-Day26-函数专题1:函数定义与参数
题目1:计算圆的面积 任务: 编写一个名为 calculate_circle_area 的函数,该函数接收圆的半径 radius 作为参数,并返回圆的面积。圆的面积 π * radius (可以使用 math.pi 作为 π 的值)要求:函数接收一个位置参数 radi…...

WebRTC调研
WebRTC是什么,为什么,如何使用 WebRTC有什么优势 WebRTC Architecture Amazon KVS WebRTC 其它厂商WebRTC 海康门禁WebRTC 海康门禁其他界面整理 威视通WebRTC 局域网 Google浏览器 Microsoft Edge 公网 RTSP RTMP NVR ONVIF SIP SRT WebRTC协…...