从Java到Kotlin(五)
函数与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(五)相关推荐
- Java 二十五载,正在 Kotlin 化!
相比 Groovy.Scala.Clojure 等来说,Kotlin 保留了 Java 所有的优点,消除了 Java 存在的大部分问题.这是它为什么如此受欢迎的主要原因,许多人认为它可以在未来几年取 ...
- 从Java到Kotlin(三)
本篇文章主要对比Java跟Kotlin中的类和接口的写法. 目录 一.类的声明 二.构造函数 三.函数的参数 四.创建类的实例 五.数据类 六.枚举类 七.属性 八.内部类 九.可见性修饰符 十.继承 ...
- 学习Kotlin(五)函数与Lambda表达式
推荐阅读: 学习Kotlin(一)为什么使用Kotlin 学习Kotlin(二)基本语法 学习Kotlin(三)类和接口 学习Kotlin(四)对象与泛型 学习Kotlin(五)函数与Lambda表达 ...
- 手把手教你学Kotlin (2):task1-6 函数,Java to Kotlin Convert,(持续更新中)
文章目录 task1:函数 task2:Java to Kotlin Convert task3:Named arguments task1:函数 先看任务介绍: 这个任务的意思是修改代码,让函数返回 ...
- 放弃java转战kotlin,我的心路历程
2019独角兽企业重金招聘Python工程师标准>>> 前言 刚开始接触kotlin语言时,其实对于大多数人来说都是反感的,涉及到改变以往的开发习惯(java),习惯很容易养成,却很 ...
- 初识未来趋势:Java与Kotlin;EclipsE与IntelliJ
Kotlin是JetBrains的一种新的编程语言.它首次出现在2011年,JetBrains推出了名为"科特林"的项目. Kotlin是开源语言. 基本上像Java一样,C和C ...
- Java 调用 Kotlin
Kotlin 和 Java 的互操作性是 Kotlin 的一大优点,Kotlin 调用 Java 已经基本不需要特别去注意什么了,但是 Java 调用 Kotlin 代码就不那么好看了.项目切换到 K ...
- Java进阶(五)Java I/O模型从BIO到NIO和Reactor模式
本文介绍了Java中的四种I/O模型,同步阻塞,同步非阻塞,多路复用,异步阻塞.同时将NIO和BIO进行了对比,并详细分析了基于NIO的Reactor模式,包括经典单线程模型以及多线程模式和多Reac ...
- 全网最细笔记java与kotlin的一些异同
本文主要介绍java与kotlin的一些异同 后面可能还会继续比较kotlin和dart 期待吗? 打印日志 Java System.out.print("Amit Shekhar" ...
最新文章
- oracle的for和i++
- 面向对象的设计原则最终篇
- 在Java程序设计中,设置环境变量path和classpath的作用分别是什么?
- NoSQL and Redis
- 深入理解Python生成器(Generator)
- 8.8 正睿暑期集训营 Day5
- [PAT乙级]1013 数素数
- AcWing 1015. 摘花生
- 【Java】NIO中Selector的select方法源码分析
- java 磁盘空间_如何使用Java查找剩余的磁盘空间?
- Mac提示app损坏、Error,Mac电脑最常见错误的解决方案
- 新模型SkipNet在ImageNet分类任务大放光彩!优化损失函数!
- oracle视图用法,oracle视图大全
- 免费的Access数据库员工管理系统下载-IT技术网站 企业人员管理系统源码
- 用友NC CLOUD 工具
- 中南大学计算机学院楠,中南比湖大更湖大,不对,应该说湖大没中南大学中南。...
- UICollectionViewCell复用时修改子页面属性出现混乱的解决方法
- Git Gitosis
- 基于Matlab对大米计数的研究
- 第二代战斗机的特点有哪些