函数与Lambda表达式

目录

一、函数声明与调用
二、参数和返回值
三、单表达式函数
四、函数作用域
五、泛型函数
六、尾递归函数
七、中缀表示法
八、Lambda表达式的语法
九、高阶函数与Lambda表达式
十、匿名函数
十一、内联函数


一、函数声明与调用

Java 中的方法使用 void 关键字声明:

void foo(){}
复制代码

Kotlin 中的函数使用 fun 关键字声明:

fun foo(){}
复制代码

用法相似,加入有一个 User 类,里面有一个 foo() 函数,调用函数的代码如下: Java代码

new User().foo();
复制代码

Kotlin代码

User().foo()
复制代码

二、参数和返回值

声明有参数的函数,代码如下: Java代码

void foo(String str, int i) {}
复制代码

Kotlin代码

fun foo(str: String, i: Int) {}
复制代码

Java先定义类型,后命名;Kotlin先命名,后定义类型,中间用冒号:分隔。两者都是多个参数中间用逗号,分隔。 如函数有返回值,代码如下: Java代码

String foo(String str, int i) {return "";
}
复制代码

Kotlin代码

fun foo(str: String, i: Int): String {return ""
}
复制代码

Java是把void替换成返回值的类型,而Kotlin是把返回值声明在函数的末尾,并用冒号:分隔。 两种语言声明参数和返回值的方式有点相似,而Kotlin还有更强大的功能,例如默认参数命名参数,如下所示: 函数参数可以有默认值,当没有给参数指定值的时候,使用默认值

//给i指定默认值为1
fun foo(str: String, i: Int = 1) {println("$str  $i")
}
//调用该函数,这个时候可以只传一个参数
foo("abc")
//运行代码,得到结果为: abc  1
复制代码

如果有默认值的参数在无默认值的参数之前,要略过有默认值的参数去给无默认值的参数指定值,要使用命名参数来指定值,有点绕我们看代码:

//有默认值的参数在无默认值的参数之前
fun foo(i: Int = 1, str: String) {println("$str  $i")
}
//foo("hello")  //编译错误
foo(str = "hello")  //编译通过,要使用参数的命名来指定值
//运行代码,得到结果为: hello  1
复制代码
  • 可变数量的参数
    函数的参数可以用 vararg 修饰符标记,表示允许将可变数量的参数传递给函数,如下所示:
//用 vararg 修饰符标记参数
fun <T> asList(vararg ts: T): List<T> {val result = ArrayList<T>()for (t in ts) // ts is an Arrayresult.add(t)return result
}val a = arrayOf(1, 2, 3)
//*a代表把a里所有元素
val list = asList(-1, 0, *a, 4)
//运行代码,得到结果为: [-1, 0, 1, 2, 3, 4]
复制代码

三、单表达式函数

在Kotlin中,如果函数的函数体只有一条语句,并且有返回值,那么可以省略函数体的大括号,变成单表达式函数。如下所示:

//函数体内只有一条语句,且有返回值
fun foo(): String{return "abc"
}
//这时可以省略大括号,变成单表达式函数
fun foo() = "abc"
复制代码

四、函数作用域

在 Kotlin 中函数可以在文件顶层声明,这意味着你不需要像一些语言如 Java 那样创建一个类来保存一个函数。此外除了顶层函数,Kotlin 中函数也可以声明在局部作用域、作为成员函数以及扩展函数。

1. 成员函数

成员函数是指在类或对象里定义的函数。

Java代码:

class User {//在类里定义函数。void foo() {}
}
//调用
new User().foo();
复制代码

Kotlin代码:

class User() {//在类里定义函数。fun foo() {}
}
//调用
User().foo()
复制代码

2. 局部函数

Kotlin支持在函数内嵌套另一个函数,嵌套在里面的函数成为局部函数,如下所示:

fun foo() {println("outside")fun inside() {println("inside")}inside()
}//调用foo()函数
foo()
复制代码

运行代码,得到结果

而Java中没有局部函数这一概念。

五、泛型函数

泛型参数使用尖括号指定,如下所示: Java代码

<T> void print(T t) {
}<T> List<T> printList(T t) {
}
复制代码

Kotlin代码

fun <T> printList(item: T) {
}fun <T> printList(item: T): List<T> {
}
复制代码

六、尾递归函数

尾递归函数是一个递归函数,用关键字tailrec来修饰,函数必须将其自身调用作为它执行的最后一个操作。当一个函数用tailrec修饰符标记并满足所需的形式时,编译器会优化该递归,留下一个快速而高效的基于循环的版本,无堆栈溢出的风险,举个例子: 先看一段代码

fun count(x: Int = 1): Int = if (x == 10) x else count(x - 1)
复制代码

上面的count()函数是一个死循环,当我们调用count()函数后,会报StackOverflowError。这时可以用tailrec修饰符标记该递归函数,并将其自身调用作为它执行的最后一个操作,如下所示:

tailrec fun count(x: Int = 1): Int = if (x == 10) x else count(x - 1)
复制代码

再次运行代码,无堆栈溢出。

七、中缀表示法

中缀表示法是调用函数的另一种方法。如果要使用中缀表示法,需要用infix 关键字来修饰函数,且要满足下列条件:

  • 它们必须是成员函数或扩展函数;
  • 它们必须只有一个参数;
  • 其参数不得接受可变数量的参数。

下面来举个例子:

//扩展函数
infix fun String.removeLetter(str: String): String {//this指调用者return this.replace(str, "")
}//调用
var str = "hello world"
//不使用中缀表示法
println(str.removeLetter("h")) //输出ello world
//使用中缀表示法
println(str removeLetter "d")  //输出hello worl
//使用中缀表示法调用str removeLetter "d"等同于调用str.removeLetter("d")//还可以连续调用
println(str.removeLetter("h").removeLetter("d").removeLetter("l")) // 输出 eo wor
println(str removeLetter "h" removeLetter "d" removeLetter "l") // 输出 eo wor
复制代码

八、Lambda表达式的语法

Lambda表达式的语法如下:

  • Lambda 表达式总是括在大括号中;
  • 其参数(如果有的话)在 -> 之前声明(参数类型可以省略);
  • 函数体(如果存在的话)在 -> 后面。

举个例子:

//这是一个Lambda表达式的完整语法形式
val sum = { x: Int, y: Int -> x + y }
//Lambda表达式在大括号中
//参数 x 和 y 在 -> 之前声明
//参数声明放在大括号内,并有参数类型标注
//函数体 x + y 在 -> 后面val i: Int = sum(1, 2)
println(i) //输出结果为 3
复制代码

如果Lambda表达式自动推断的返回类型不是Unit,那么在Lambda表达式函数体中,会把最后一条表达式的值当做是返回值。所以上面的常量sum 的返回值是Int类型。如果要指定常量sum的返回值为Int类型,可以这样写:

val sum: (Int, Int) -> Int = { x, y -> x + y }val i: Int = sum(1, 2)
println(i) //输出结果为 3
复制代码

当Lambda表达式只有一个参数的时候,那么它将可以省略这个唯一的参数的定义,连同->也可以省略。如下所示:

//当Lambda表达式只有一个参数的时候
val getInt: (Int) -> Int = { x -> x + 1 }
val int = getInt(2)
println(int)  //输出结果为:3//可以省略这个参数的定义
//并且将隐含地奖这个参数命名为 it
val sum: (Int) -> Int = { it + 1 }
val int = sum(2)
println(int)  //输出结果为:3
复制代码

上面说到如果Lambda表达式自动推断的返回类型不是Unit,那么在Lambda表达式函数体中,会把最后一条表达式的值当做是返回值。举个例子:

var sum: (Int) -> Int = {val i: Int = it + 1val j: Int = i + 3val k: Int = it + j - iikj
}
println(sum(1))
//输出结果为 5,也就是 j 的值
复制代码

九、高阶函数与Lambda表达式

高阶函数是将函数用作参数或返回值的函数,如下所示:

fun getName(name: String): String {return name
}fun printName(a: String, name: (str: String) -> String): String {var str = "$a${name("Czh")}"return str
}//调用
println(printName("Name:", ::getName))
//运行代码,输出 Name:Czh
复制代码

上面代码中name: (str: String) -> String是一个函数,拥有函数类型() -> String,接收一个String参数,当我们执行var str = "$a${name("Czh")}"这行代码的时候,相当于执行了var str = "$a${getName("Czh")}",并返回了字符串"Czh"。当我们调用printName("Name:", ::getName)时,将函数作为参数传入高阶函数,需要在该函数前加两个冒号::作为标记。

Kotlin提供了Lambda表达式来让我们更方便地传递函数参数值。Lambda表达式总是被大括号括着;如果有参数的话,其参数在 -> 之前声明,参数类型可以省略;如果存在函数体的话,函数体在-> 后面,如下所示:

println(printName("Name:", { name -> getName("Czh") }))
//运行代码,输出 Name:Czh
复制代码

如果函数的最后一个参数是一个函数,并且你传递一个Lambda表达 式作为相应的参数,你可以在圆括号()之外指定它,如下所示:

println(printName("Name:") { name -> getName("Czh") })
//运行代码,输出 Name:Czh
复制代码

十、匿名函数

匿名函数与常规函数一样,只是省略了函数名称而已。举个例子

fun(x: Int, y: Int): Int = x + y
复制代码

匿名函数函数体是表达式,也可以是代码段,如下所示:

fun(x: Int, y: Int): Int {return x + y
}
复制代码

上面高阶函数的例子中的printName函数的第二个参数也可以传入一个匿名函数,如下所示:

println(printName("Name:", fun(str: String): String { return "Czh" }))
//运行代码,输出 Name:Czh
复制代码

十一、内联函数

1.内联函数

使用高阶函数会带来一些运行时的效率损失。每一个函数都是一个对象,并且会捕获一个闭包。 即那些在函数体内会访问到的变量。 内存分配(对于函数对象和类)和虚拟调用会引入运行时间开销。这时可以通过内联函数消除这类的开销。举个例子:

fun printName(a: String, name: (str: String) -> String): String {var str = "$a${name("Czh")}"return str
}println(printName("Name:", { name -> getName("Czh") }))
复制代码

上面代码中,printName函数有一个函数类型的参数,通过Lambda表达式向printName函数传入参数值,Kotlin编译器会为Lambda表达式单独创建一个对象,再将Lambda表达式转换为相应的函数并调用。如果这种情况出现比较多的时候,就会很消耗资源。这是可以在函数前使用inline关键字,把Lambda函数内联到调用处。如下所示:

inline fun printName(a: String, name: (str: String) -> String): String {var str = "$a${name("Czh")}"return str
}println(printName("Name:", { name -> getName("Czh") }))
复制代码

2.禁用内联

通过inline关键字,编译器将Lambda函数内联到调用处,消除了运行时消耗。但内联可能导致生成的代码增加,所以需要避免内联比较大的Lambda表达式。如果想禁用一些Lambda函数的内联,可以使用noinline修饰符禁用该Lambda函数的内联,如下所示:

inline fun printName(name1: (str1: String) -> String, noinline name2: (str2: String) -> String): String {var str = "${name1("Name:")}${name2("Czh")}"return str
}
复制代码

3.内联属性

inline关键字除了可以使函数内联之外,还能内联没有幕后字段(field)的属性,如下所示:

val foo: Fooinline get() = Foo()var bar: Barget() = ……inline set(v) { …… }
复制代码

总结

本篇文章对比了Java方法和Kotlin函数在写法上的区别,也认识了Lambda函数和还列举了一些Kotlin函数中比较特别的语法,如中缀表示法等。可见Kotlin中的函数内容还是很多的,用法也相对复杂,但运用好Kotlin的函数,能使开发变得更简单。

参考文献:
Kotlin语言中文站、《Kotlin程序开发入门精要》

推荐阅读:
从Java到Kotlin(一)为什么使用Kotlin
从Java到Kotlin(二)基本语法
从Java到Kotlin(三)类和接口
从Java到Kotlin(四)对象与泛型
从Java到Kotlin(五)函数与Lambda表达式
从Java到Kotlin(六)扩展与委托
从Java到Kotlin(七)反射和注解
从Java到Kotlin(八)Kotlin的其他技术
Kotlin学习资料总汇


更多精彩文章请扫描下方二维码关注微信公众号"AndroidCzh":这里将长期为您分享原创文章、Android开发经验等! QQ交流群: 705929135

从Java到Kotlin(五)相关推荐

  1. Java 二十五载,正在 Kotlin 化!

    相比 Groovy.Scala.Clojure 等来说,Kotlin 保留了 Java 所有的优点,消除了 Java 存在的大部分问题.这是它为什么如此受欢迎的主要原因,许多人认为它可以在未来几年取 ...

  2. 从Java到Kotlin(三)

    本篇文章主要对比Java跟Kotlin中的类和接口的写法. 目录 一.类的声明 二.构造函数 三.函数的参数 四.创建类的实例 五.数据类 六.枚举类 七.属性 八.内部类 九.可见性修饰符 十.继承 ...

  3. 学习Kotlin(五)函数与Lambda表达式

    推荐阅读: 学习Kotlin(一)为什么使用Kotlin 学习Kotlin(二)基本语法 学习Kotlin(三)类和接口 学习Kotlin(四)对象与泛型 学习Kotlin(五)函数与Lambda表达 ...

  4. 手把手教你学Kotlin (2):task1-6 函数,Java to Kotlin Convert,(持续更新中)

    文章目录 task1:函数 task2:Java to Kotlin Convert task3:Named arguments task1:函数 先看任务介绍: 这个任务的意思是修改代码,让函数返回 ...

  5. 放弃java转战kotlin,我的心路历程

    2019独角兽企业重金招聘Python工程师标准>>> 前言 刚开始接触kotlin语言时,其实对于大多数人来说都是反感的,涉及到改变以往的开发习惯(java),习惯很容易养成,却很 ...

  6. 初识未来趋势:Java与Kotlin;EclipsE与IntelliJ

    Kotlin是JetBrains的一种新的编程语言.它首次出现在2011年,JetBrains推出了名为"科特林"的项目. Kotlin是开源语言. 基本上像Java一样,C和C ...

  7. Java 调用 Kotlin

    Kotlin 和 Java 的互操作性是 Kotlin 的一大优点,Kotlin 调用 Java 已经基本不需要特别去注意什么了,但是 Java 调用 Kotlin 代码就不那么好看了.项目切换到 K ...

  8. Java进阶(五)Java I/O模型从BIO到NIO和Reactor模式

    本文介绍了Java中的四种I/O模型,同步阻塞,同步非阻塞,多路复用,异步阻塞.同时将NIO和BIO进行了对比,并详细分析了基于NIO的Reactor模式,包括经典单线程模型以及多线程模式和多Reac ...

  9. 全网最细笔记java与kotlin的一些异同

    本文主要介绍java与kotlin的一些异同 后面可能还会继续比较kotlin和dart 期待吗? 打印日志 Java System.out.print("Amit Shekhar" ...

最新文章

  1. oracle的for和i++
  2. 面向对象的设计原则最终篇
  3. 在Java程序设计中,设置环境变量path和classpath的作用分别是什么?
  4. NoSQL and Redis
  5. 深入理解Python生成器(Generator)
  6. 8.8 正睿暑期集训营 Day5
  7. [PAT乙级]1013 数素数
  8. AcWing 1015. 摘花生
  9. 【Java】NIO中Selector的select方法源码分析
  10. java 磁盘空间_如何使用Java查找剩余的磁盘空间?
  11. Mac提示app损坏、Error,Mac电脑最常见错误的解决方案
  12. 新模型SkipNet在ImageNet分类任务大放光彩!优化损失函数!
  13. oracle视图用法,oracle视图大全
  14. 免费的Access数据库员工管理系统下载-IT技术网站 企业人员管理系统源码
  15. 用友NC CLOUD 工具
  16. 中南大学计算机学院楠,中南比湖大更湖大,不对,应该说湖大没中南大学中南。...
  17. UICollectionViewCell复用时修改子页面属性出现混乱的解决方法
  18. Git Gitosis
  19. 基于Matlab对大米计数的研究
  20. 第二代战斗机的特点有哪些

热门文章

  1. Good Bye G.cn
  2. java时间戳求时间差_时间戳换算后,截取时间相差8小时的问题
  3. 第十七单元 Samba服务
  4. 图解TCP/IP(第5版)PDF
  5. 后台返回的数据换行显示
  6. 【Spark篇】---Spark初始
  7. MYSQL创建多张表,相同表结构,不同表名
  8. python-MySQLdb-练习
  9. OpenCV笔记(Size)
  10. load-store/register-memory/register-plus-memory比较