函数和Lambda表达式

Kotlin融合了面向过程语言和面向对象语言的特征,相比于Java,它增加了对函数式编程的支持,支持定义函数、调用函数。相比于C语言,Kotlin支持局部函数(Lambda表达式的基础)。

6.1 函数入门

6.1.1 定义和调用函数

定义函数的语法格式如下:

  1. fun 函数名 (形参列表) [: 返回值类型] {
  2. //函数体
  3. }
  4. // 函数的声明必须使用fun关键字
  5. // 形参列表 “形参名: 参数类型”
  6. // 举例:
  7. fun max(x: Int, y: Int): Int {
  8. val res = if(x > y) x else y
  9. return res
  10. }

6.1.2 函数返回值和Unit

如果希望一个函数没有返回值,可以通过以下两种方式实现:

  1. 省略“: 返回值类型”部分
  2. 使用“: Unit”指定返回Unit -> 代表没有返回值
  1. fun func1(str: String) {
  2. println(str)
  3. }
  4. fun func2(str: String): Unit {
  5. println(str)
  6. }

6.1.3 递归函数

在函数体内调用该函数本身。

......

6.1.4 单表达式函数

如果函数只是返回单个表达式,那么可以省略花括号并在函数名后用等号指定函数体。这种形式的函数被称为单表达式函数。

  1. fun area1(x: Int, y: Int): Int {
  2. return x*y;
  3. }
  4. fun area2(x: Int, y: Int): Int = x*y
  5. //函数area1和函数area2等效,其中area2是单表达式函数

6.2 函数的形参

相比于Java,Kotlin形参的功能更加丰富且灵活。

6.2.1 命名参数

Kotlin中参数名是有意义的。

Kotlin除了可以按照传统的方式——根据参数的位置(顺序)来传入参数之外,还可以使用命名参数来传参(此时可以不按顺序传参)。

还可以以上二者混合使用,但此时要求位置参数必须位于命名参数之前。

  1. fun area(width: Double, height: Double): Double {
  2. var a = width * height
  3. println("width=${width}, height=${height}, area=${a}")
  4. return a
  5. }
  6. var res: Double
  7. //传统的传参方式——位置参数,根据形参位置传参
  8. res = area(2.0, 5.0)
  9. //Kotlin中支持的传参方式——命名参数,根据形参名传参
  10. res = area(heigth = 5.0, width = 2.0)
  11. //二者的混合使用
  12. res = area(2.0, height = 5.0)

6.2.2 形参默认值

在Kotlin中,用户可以在定义函数时为一个或多个形参指定默认值——这样在调用函数时,就可以省略该形参值的传入,而使用形参默认值。语法格式如下:

形参名: 形参类型 = 默认值

  1. fun sayHi(name: String = "Seraph", msg: String = "hello !") {
  2. println("${name}, ${msg}")
  3. }
  4. sayHi() //全部使用默认参数,输出:Seraph, hello !
  5. sayHi("Jack", "see you !") //全部使用位置参数
  6. sayHi(name = "Leo", msg = "bye~") //全部使用命名参数
  7. sayHi(msg = "bye~") //默认参数 + 命名参数
  8. sayHi("Ben") // 位置参数 + 默认参数, 输出:Ben, hello !

通常建议将带默认值的参数定义在形参列表的最后。

6.2.3 尾递归函数

当函数将自身作为其执行体的最后一行代码,且递归调用后没有更多的代码时,可以使用尾递归语法。

尾递归不能在try、catch、finally块中使用。

尾递归函数需要使用tailrec修饰。

  1. //定义计算阶乘的函数
  2. fun fact(n: Int): Int {
  3. if(n == 1) {
  4. return 1
  5. } else {
  6. return n * fact(n-1)
  7. }
  8. }
  9. //使用尾递归语法改写上述函数
  10. tailrec fun factRec(n: Int, total: Int = 1): Int = if(n == 1) total else factRec(n-1, total * n)

编译器会将尾递归优化成一个快速且高效的基于循环的版本,这样可以减少对内存的消耗。

6.2.4 个数可变的形参

Kotlin中,我们可以通过在形参名前添加 vararg修饰符,以定义个数可变的形参,从而为函数指定数量不定的形参。

  1. fun test(a: Int, vararg books: String) {
  2. //books被当做数组处理
  3. for(book in books) {
  4. println(book)
  5. }
  6. }

Kotlin允许个数可变的形参位于参数列表中的任意位置,但是要求一个函数只能带一个形参。

6.3 函数重载

一个kt文件中包含了两个或两个以上函数名相同、形参列表不同的函数,被称为函数的重载。

与Java类似的是,Kotlin中函数的重载也只能通过形参列表进行区分,形参个数不同、形参类型不同都可以算作函数的重载。但是形参名不同、返回值类型不同或修饰符不同,都不能算函数的重载。

6.4 局部函数

Kotlin支持在函数体内定义函数,这种定义在函数体内的函数别称之为局部函数。

默认情况下,局部函数是对外部隐藏的,只在其封闭函数内有效。其封闭函数也可以返回局部函数,以便程序在其他作用域中使用局部函数。

  1. fun A(type: String) {
  2. fun B(name: String) {
  3. println("name=${name}")
  4. }
  5. }

6.5 高阶函数

Kotlin中函数也是一等公民,函数本身也有自己的类型——函数类型。函数类型既可以用于定义变量,也可以用于形参、返回值。

6.5.1 使用函数类型

函数类型由:函数形参列表、-> 、返回值类型 这三者组成。

  1. fun foo(a: Int, name: String) -> String {
  2. ......
  3. }
  4. // 函数foo的函数类型为:(Int, String) -> String
  5. fun bar() {
  6. ......
  7. }
  8. // 函数bar的函数类型为:() 或 () -> Unit

使用函数类型定义变量的方法如下:

  1. var myFun: (Int, Int) -> Int
  2. fun pow(base: Int, exponent: Int): Int {
  3. var res = 1
  4. for(i in 1 .. exponent) {
  5. res *= base
  6. }
  7. return res
  8. }
  9. fun area(width: Int, height: Int): Int = width * height
  10. myFun = ::pow //将pow函数赋值给myFun
  11. println(myFun(2, 3)) //输出:8
  12. myFun = ::pow //将area函数赋给myFun
  13. println(myFun(2, 3)) //输出:6

当直接访问一个函数的函数引用,而不是调用该函数时,需要在函数名前添加两个冒号,而且不能在函数名后添加圆括号——一旦添加圆括号就变成了调用函数而不是访问函数引用。

6.5.2 使用函数类型作为形参

  1. fun map(data: Int, fn: (Int) -> Int): Unit {
  2. ......
  3. }

6.5.3 使用函数类型作为返回值类型

  1. fun getMathFunc(type: String): (Int) -> Int {
  2. fun square(n: Int): Int = n * n
  3. fun cube(n: Int): Int = n * n * n
  4. fun factorial(n: Int): Int {
  5. var res = 1;
  6. for(index in 2 .. n) {
  7. res *= index
  8. }
  9. return res
  10. }
  11. when(type) {
  12. "square" -> return ::square
  13. "cube" -> return ::cube
  14. else -> return ::factorial
  15. }
  16. }

6.6 局部函数与Lambda表达式

Lambda表达式是现代编程语言争相引入的语法,它是功能更灵活的代码块,可以在程序中被传递和调用。

6.6.1 回顾局部函数

6.6.2 使用Lambda表达式代替局部函数

可以使用Lambda表达式来简化局部函数。

  1. fun getMathFunc(type: String): (Int) -> Int {
  2. when(type) {
  3. "square" -> return {n: Int ->
  4. n * n
  5. }
  6. "cube" -> return {n: Int ->
  7. n * n * n
  8. }
  9. else -> return { n: Int ->
  10. var res = 1
  11. for(index in 2 .. n) {
  12. res *= index
  13. }
  14. res
  15. }
  16. }
  17. }

Lambda表达式和局部函数区别如下:

  • Lambda表达式总是被大括号括着
  • 定义Lambda表达式不需要fun关键字,无需指定函数名
  • 形参列表(如果有)在 -> 之前声明,参数类型可以省略。
  • 函数体(Lambda表达式的执行体)放在 -> 之后
  • 函数的最后一个表达式自动被作为Lambda表达式的返回值,无需使用 return 关键字

6.6.3 Lambda表达式的脱离

作为参数传入的Lambda表达式可以脱离函数独立使用。

6.7 Lambda表达式

Lambda表达式语法如下:

  1. { (形参列表) ->
  2. 零到多条可执行语句
  3. }

6.7.1 调用Lambda表达式

Lambda表达式可以被赋值给变量或者直接调用。

  1. // 定义一个Lambda表达式,并将它赋值给square
  2. var square = { n: Int ->
  3. n * n
  4. }
  5. println(square(5)) //输出:25
  6. // 定义一个Lambda表达式,并在其后添加圆括号来调用该表达式,并将结果赋给res
  7. var res = { base: Int, exponent: Int ->
  8. var result = 1
  9. for(i in 1 .. exponent) {
  10. result *= base
  11. }
  12. result
  13. }(4, 3)
  14. println(res) //输出64

6.7.2 利用上下文推断类型

如果Kotlin根据Lambda表达式的上下文推断出形参类型,那么Lambda表达式就可以省略形参类型。

  1. // 变量square的类型已经被声明了,因此Kotlin可以推断出Lambda表达式的形参类型
  2. // 所以Lambda表达式可以省略形参类型
  3. var square: (Int) -> Int = {n -> n * n}
  4. var res = {a, b -> a + b} //非法,因为Kotlin无法推断形参a,b的数据类型

6.7.3 省略形参名

如果只有一个形参,那么Kotlin允许省略Lambda表达式的形参名。当形参名被省略时,Lambda表达式用 it来代表形参。同时, -> 也不需要了。

  1. // 省略形参名,用it代替
  2. var square: (Int) -> Int = {it * it}

6.7.4 调用Lambda表达式的约定

Kotlin中约定:如果函数的最后一个形参是函数类型,且你打算传入一个Lambda表达式作为相应的参数,那么就允许在圆括号之外指定Lambda表达式。

  1. var list = listOf("Java", "Kotlin", "Go")
  2. var rt = list.dropWhile(){it.length > 3} //dropWhile()方法的参数列表为: (T) -> Boolean
  3. println(rt) //输出: [Go]

如果Lambda表达式是函数调用的唯一参数,则调用时函数的圆括号可以省略:

  1. var rt = list.dropWhile{it.length > 3}

6.7.5 个数可变的参数和Lambda参数

前面有提到:个数可变的形参可以定义在参数列表中的任意位置,但是如果不将它放在最后,就只能用命名参数的形式为可变形参之后的其他形参传值。所以建议将可变形参放在形参列表的最后。

那么我们到底是应该将函数类型的参数放在形参列表的最后,还是将个数可变的参数放在形参列表最后呢?

答案:如果一个函数既包含个数可变的形参,也包含函数类型的形参,那么应该将函数类型的形参放在最后。

  1. fun <T> test(vararg name: String, transform: (String) -> T): List<T> {
  2. ......
  3. }

6.8 匿名函数

Lambda表达式无法指定返回值类型,且在一些特殊场景下Kotlin也无法推断出Lambda表达式的返回值类型。此时可以匿名函数来代替Lambda表达式。

只要将普通函数的函数名去掉就成了匿名函数。

  1. var test = fun(x: Int, y: Int): Int {
  2. return x + y
  3. }

6.9 捕获上下文中的变量和常量

Lambda表达式、匿名函数、局部函数都可访问或修改其所在上下文中的变量和常量。

6.10 内联函数

由于函数的调用过程会产生一定的时间和空间上的开销,为了避免这部分开销(即避免产生函数的调用过程),我们可以考虑通过内联函数 的方式,将被调用的函数或表达式”嵌入“原来的执行流中。

  1. // 通过inline关键字声明内联函数
  2. inline fun mFun(data: Int) {
  3. ......
  4. }

《疯狂Kotlin讲义》读书笔记6——函数和Lambda表达式的更多相关文章

  1. Kotlin语法(函数和lambda表达式)

    三.函数和lambda表达式 1. 函数声明 fun double(x: Int): Int { } 函数参数是用 Pascal 符号定义的 name:type.参数之间用逗号隔开,每个参数必须指明类 ...

  2. 3 委托、匿名函数、lambda表达式

    委托.匿名函数.lambda表达式 在 2.0 之前的 C# 版本中,声明委托的唯一方法是使用命名方法.C# 2.0 引入了匿名方法,而在 C# 3.0 及更高版本中,Lambda 表达式取代了匿名方 ...

  3. 浅析匿名函数、lambda表达式、闭包(closure)区别与作用

    浅析匿名函数.lambda表达式.闭包(closure)区别与作用 所有的主流编程语言都对函数式编程有支持,比如c++11.python和java中有lambda表达式.lua和JavaScript中 ...

  4. 委托,匿名函数和lambda表达式

    很早之前就接触到了委托,但是一直对他用的不是太多,主要是本人是菜鸟,能写的比较高级的代码确实不多,但是最近在看MSDN微软的类库的时候,发现了微软的类库好多都用到了委托,于是决定好好的研究研究,加深一 ...

  5. 匿名函数:Lambda表达式和匿名方法

    匿名函数一个"内联"语句或表达式,可在需要委托类型的任何地方使用.可以使用匿名函数来初始化命名委托,或传递命名委托(而不是命名委托类型)作为方法参数. 共有两种匿名函数: Lamb ...

  6. python函数,lambda表达式,三目运算,列表解析,递归

    一.自定义函数 定义函数时,函数体不执行:只有在调用函数时,函数体才执行.函数的结构: 1. def 2. 函数名 3. 函数体 def func_name(): 函数体 4. 返回值 如果没有声明返 ...

  7. python3 入门 (三) 函数与lambda表达式、闭包

    函数 是组织好的.可重复使用的.用来实现单一或相关联功能的代码段. 函数代码块以def关键词开头,后接函数标识符名称和圆括号() 任何传入参数和自变量必须放在圆括号中间.圆括号之间可以用于定义参数 函 ...

  8. C#语法之匿名函数和Lambda表达式

    上一篇博客主要是对委托和事件做了一小结,这篇是在上一篇博客的基础上对匿名函数和Lambda表达式小结.还是接着上一篇说起,在上一篇中也说了委托是一种数据结构,主要是解决让函数作为参数的问题.在使用委托 ...

  9. Python内嵌函数与Lambda表达式

    //2018.10.29 内嵌函数与lambda 表达式 1.如果在内嵌函数中需要改变全局变量的时候需要用到global语句对于变 量进行一定的说明与定义 2.内部的嵌套函数不可以直接在外部进行访问 ...

随机推荐

  1. Course2.1 Graph Paper Programming

    Overview 通过日常生活中的活动来体验程序算法,目标时能够将现实世界的场景与程序场景关联起来. Objective 抓住将现实世界问题转换为程序的难点: 你认为非常明确的指令在计算机看来可能还是 ...

  2. (三)String、StringBuilder、StringBuffer在字符串操作中的性能差异浅析

    参考资料:https://www.iteye.com/blog/hank4ever-581463 <Core Java Volume I-Fundamentals>原书第十版 <Ja ...

  3. 05.从0实现一个JVM语言之目标平台代码生成-CodeGenerator

    从0实现JVM语言之目标平台代码生成-CodeGenerator 源码github仓库, 如果这个系列文章对你有帮助, 希望获得你的一个star 本节相关代码生成package地址 阶段性的告别 非常 ...

  4. roarctf_2019_realloc_magic

    目录 roarctf_2019_realloc_magic 总结 题目分析 checksec 函数分析 解题思路 初步解题思路 存在的问题 问题解决方案 最终解决思路 编写exp exp说明 roar ...

  5. 给出镜像FreeBSD 基本要求

    硬盘 ports 500G update 500G portsnap 500G pkg arm64 amd64 i386 11-12-13 4TB 网络流量一个月专线大概2w RMB CPU 内存 其 ...

  6. C# 基础 - Json 之间的转换

    这里举例两种方式. 1. Newtonsoft.Json.JsonConvert 需要引用外部的 Newtonsoft.Json.dll /// <summary> /// 将json字符 ...

  7. java注解基础入门

    前言 这篇博客主要是对java注解相关的知识进行入门级的讲解,包括**,核心内容主要体现在对java注解的理解以及如何使用.希望通过写这篇博客的过程中让自己对java注解有更深入的理解,在工作中可以巧 ...

  8. MySQL优化从执行计划开始(explain超详细)

    前言 小伙伴一定遇到过这样反馈:这页面加载数据太慢啦,甚至有的超时了,用户体验极差,需要赶紧优化: 反馈等同于投诉啊,多有几次,估计领导要找你谈话啦. 于是不得不停下手里头的活,赶紧进行排查,最终可能 ...

  9. 在ASP.NET Core中用HttpClient(四)——提高性能和优化内存

    到目前为止,我们一直在使用字符串创建请求体,并读取响应的内容.但是我们可以通过使用流提高性能和优化内存.因此,在本文中,我们将学习如何在请求和响应中使用HttpClient流. 什么是流 流是以文件. ...

  10. 【vue开发】 计算属性传参

    <template> <div> {{test('zhende', 'np')}} </div> </template> <script> ...