前言
真是人生无常,大肠包小肠啊。刚学完基础知识,感觉kotlin还蛮简单的,基本语法跟java极其相似,我还满怀信心的想着这不得一周就把kotlin学完啊。事实证明我真的想多了。看完函数的知识我就已经懵了,然后我又怀着不信邪的态度看了看后面类和对象。缓了一段时间,我想说,我们要保持微笑,勇敢的战胜困难,奥里给!!!
上面就是发一下牢骚,该学还得学啊,那么就开始吧。
普通函数
一、无参无返回值
先来看一下一个无参无返回值的函数
fun printInfo(){
println("这是一条打印信息")
}
复制代码
看着挺简单的,其实它里面还有一些隐藏的东西。首先可见性在kotlin中如果没有写的话是默认public。然后无返回值其实是返回了一个Unit对象,Unit对象代表的就是无返回值,但是Unit又是一个对象,所以就相当于函数返回了一个无返回值的Unit对象。这段话确实有点绕,还是看一下上面这段代码写全是个什么样子吧。
public final fun printInfo(): Unit {
return println("这是一条打印信息")
}
复制代码
我刚开始看到这里的时候我很无语,还可以这样的?好吧,其实如果不理解的话也没关系,这个只作为了解就行。只是为了让大家知道它里面的一些隐藏信息。当然正常工作中我们不可能这样写的。
二、有参有返回值
fun printPersonalInfo(name: String, sex: String, age: Int, birthday: String): String {
return "我的名字是$name,性别$sex,年龄$age,出生日期$birthday"
}
复制代码
如上面这段代码,打印个人信息。基本跟java类似,唯一不同的就是不管参数类型还是返回值类型,都是放到后面的。
其实我还是习惯像下面这样写,这样写的话感觉结构清晰,上面那样如果参数过多还得换行看。
fun printPersonalInfo(
name: String,
sex: String,
age: Int,
birthday: String
): String {
return "我的名字是$name,性别$sex,年龄$age,出生日期$birthday"
}
复制代码
调用此函数跟java一样
val test = Test()
test.printPersonalInfo("张三", "男", 18, "2020-01-01")
复制代码
三、默认参数
我们也可以给参数设置默认值,如下代码
fun printPersonalInfo(
name: String = "张三",
sex: String = "男",
age: Int = 20,
birthday: String = "2020-01-01"
): String {
return "我的名字是$name,性别$sex,年龄$age,出生日期$birthday"
}
复制代码
如果当前函数的参数有默认值,我们调用的时候就可以不写入参,或者写其中几个入参,或者入参全写。就像下面代码这样使用
val test = Test()
test.printPersonalInfo()
test.printPersonalInfo("张三")
test.printPersonalInfo("张三", "男")
test.printPersonalInfo("张三", "男", 18, "2020-01-01")
复制代码
四、具名参数
一般情况下,如果函数参数过多,在我们调用函数的时候会感觉很乱,使用具名参数可以使参数更加清晰一些
val test = Test()
test.printPersonalInfo()
test.printPersonalInfo(name = "张三")
test.printPersonalInfo(name = "张三", sex = "男")
test.printPersonalInfo(name = "张三", sex = "男", age = 18, birthday = "2020-01-01")
复制代码
五、可变参数
看下面两个例子,这是可变参数的基本用法
fun printPersonalInfo(vararg infos: String) {
println("姓名:${infos[0]},性别${infos[1]}")
}
复制代码
val test = Test()
test.printPersonalInfo("张三", "男")
复制代码
当然有时候,我们除了可变参数意外,还可能有别的参数,那么我们可以用具名参数搭配使用调用该函数
fun printPersonalInfo(person: Person,vararg infos: String) {
println("姓名:${infos[0]},性别${infos[1]}")
}
复制代码
val test = Test()
val person1 = Person()
test.printPersonalInfo(person = person1,"张三", "男")
复制代码
单表达式函数
但表达式函数也很好理解,重点就在于函数体只有一句代码,中间用“=”相连,不需要大括号了,例如
fun printPersonalInfo(
name: String,
age: Int
) = println("姓名:$name,年龄:$age")
复制代码
fun add(a: Int, b: Int): Int = a + b
复制代码
高阶函数
概念:在一个名为 x 的函数中,如果有参数为函数,或者返回值为函数,那么该函数 x 就为高阶函数。
从概念上很好理解,但是具体写起来会有一定的难度。所以我并不准备直接上一个高阶函数的例子,而是从一个个简单的步骤慢慢扩展到高阶函数。
一、无参无返回值函数类型
val printInfo: () -> Unit
复制代码
上面这段代码,声明了一个无参无返回值的函数类型。printInfo是它的函数名,小括号里没有值代表它没有函数参数,箭头后面是函数返回类型,Unit代表它没有返回值。这是语法规则,强记就好。
注意这里是用val
声明的,不是用fun,所以它是一个类型。并且它的返回值类型是Unit,所以它是一个无参无返回值的函数类型。
然后写它的函数体,如下代码
printInfo = {
println("打印一条信息")
}
复制代码
然后调用该函数就跟调用普通函数一样
printInfo()
复制代码
为了容易理解,我们是把函数类型声明和赋值分开了,其实合起来应该是下面代码这样的。注意这里的Unit不可省略
val printInfo: () -> Unit = {
println("打印一条信息")
}
复制代码
其实上面这些代码,如果我们用普通函数去写的话,就跟下面这段代码意思是一样的
fun printInfo(){
println("打印一条信息")
}
复制代码
二、有参有返回值函数类型
依照无参无返回值函数类型应该可以推断出来有参有返回值函数类型应该怎么写吧?如下代码
val printInfo: (name: String) -> String = {
"姓名:$it"
}
println(printInfo("张三"))
复制代码
注:有返回值的时候,使用lambda表达式,函数体内最后一行相当于返回值,不用写return
三、写一个简单的高阶函数
高阶函数的概念都理解了,那么写一个简单的高阶函数。需求:我们需要打印姓名的姓和名:
/**
* 打印姓名信息
*/
fun printNameInfo(
fullName: String = "张三",
getInfo: (surname: String, name: String) -> String
): String {
val surname = fullName.substring(startIndex = 0, endIndex = 1)
val name = fullName.substring(startIndex = 1)
return getInfo(surname, name)
}
//调用高阶函数
val nameInfo = printNameInfo(fullName = "张三丰") { surname, name ->
"姓:$surname\n名:$name"
}
//打印信息
println(nameInfo)
复制代码
打印结果:

刚开始学的时候,一下子看到这样的代码有点理解不了。没关系我们一步步慢慢分析。
- 首先我们先不管高阶函数体,我们可以写成下面这样易懂的代码。printNameInfo高阶函数有两个参数,一个是String类型,一个是函数类型,返回值是String类型。
fun printNameInfo(
fullName: String = "张三",
getInfo: (surname: String, name: String) -> String
): String {
return ""
}
复制代码
- 加上函数体。函数体内代码不过是分割了fullName,然后把姓和名作为参数分别传入函数参数内。等函数参数处理
fun printNameInfo(
fullName: String = "张三",
getInfo: (surname: String, name: String) -> String
): String {
val surname = fullName.substring(startIndex = 0, endIndex = 1)
val name = fullName.substring(startIndex = 1)
return getInfo(surname, name)
}
复制代码
- 调用高阶函数。一个有两个入参,先传进入fullName。函数参数可以写到参数外部并且用{}包含。函数参数内部处理逻辑由调用的时候指定。
//调用高阶函数
val nameInfo = printNameInfo(fullName = "张三丰") { surname, name ->
"姓:$surname\n名:$name"
}
复制代码
内联函数
我们在使用高阶函数的时候会带来一些性能的损耗问题,因为kotlin代码编译为字节码文件后高阶函数会生成一个匿名内部类。当然有的同学会说创建一个匿名内部类而已,能损耗多少性能呢?况且等内部类里面的代码运行完以后资源就释放了。在此,我只能对这位同学说,小了,格局小了。万一这个高阶函数调用是运行在一个循环里面呢?例如下面的一个简单的例子:
class Test {
fun printInfo(
print: () -> Unit
) {
print()
}
}
fun main() {
val test = Test()
for (n in 1..100) {
test.printInfo {
println("hello word")
}
}
}
复制代码
在这个例子中高阶函数是调用在一个循环里面,这样性能消耗是不是刷的一下子就上去了?
kotlin编译和反编译
我们知道kotlin跟java一样,也是编译成字节码文件最后运行在JVM上的。AndroidStudio也给我们提供了把kotlin文件编译成字节码文件的功能,如下图:

这样我们就可以得到当前kotlin文件的字节码文件,然后我们点击左上角的反编译按钮,就可以得到一个反编译后的java文件。如下图:

从反编译过来的java代码中,我们可以看到循环内部确实是调用了printInfo函数,函数参数是一个匿名内部类的实例。如下图:

为了防止这种情况下造成的性能损耗问题,kotlin中有一个伟大的神器—inline。
inline
inline是个啥?又是干什么用的呢?我们先不解释,直接上代码体会一下你就明白了。
首先在声明高阶函数前面加上inline关键字
inline fun printInfo(
print: () -> Unit
) {
print()
}
复制代码
然后我们再看反编译成的java代码,如下图所示:

对比不加inline反编译后的代码,是不是有很大的不同啊。加上inline关键字后相当于是直接把调用栈的内容复制出来平铺展开了。这样做的优点第一可以少一层调用栈,第二可以少创建一个匿名内部类。这样的话是不是在性能上面有很大的提升啊。
但是inline也不是万能的,如果我们在多个地方调用这个函数。编译成的字节码文件都会把代码平铺展开,这就形成了反优化,所以inline关键字得酌情使用啊。
noinline
noinline是作用于函数的参数或者函数类型的参数。用noinline关键字标记的参数就不会参与内联了。比如说如下代码:

我们需要返回一个函数类型的对象after,如果这个高阶函数使用了inline标记为内联函数,那么AndroidStudio会报错,提示让我们把after标记为noinline。
因为我们知道添加了inline的高阶函数,调用的时候会直接把代码铺开。里面的函数类型的参数就不属于对象了,所以既然after不属于对象,都找不到这个名为after的对象,那么怎么能return呢?对不对?所以这时候我们需要把after标记为noinline,这样它就不参与内联了,代码也就不会被铺开,那么它还是个对象,也就可以return了。
那么我们什么时候添加noinline呢?这个不用我们考虑,AndroidStudio会自动帮我们的。如果发生这种情况就会报错,提示让我们加上noinline。当然,如果有时间也可以自己手动把它内部代码铺开研究一下。
crossinline
这个关键字用到的时候很少,也是作用于参数或者函数类型的参数的。这里只说一下它的概念就好了。只有在内联函数中间接调用了函数类型的参数的时候,AndroidStudio就会提示我们加上crossinline。
指定函数接收者
有的时候我们声明了一个函数,但是我们只想用一种数据类型的数据去调用它,这时候就可以指定函数接收者,如下
fun String.printInfo(): Int {
return this.length
}
println("你是我的眼哈".printInfo())
复制代码
函数接收者在函数内部可以用this来表示
中缀表示法
中缀表示法必须用infix关键字,而且它还有一些必要条件:
- 它们必须是成员函数或扩展函数
- 它们必须只有一个参数;
- 其参数不得接受可变数量的参数且不能有默认值
ok,那么写个例子看看怎么用
fun merge() {
val num = 1 add 2
println(num)
}
infix fun Int.add(num: Int): Int {
return this + num
}
复制代码
尾递归函数
使用tailrec修饰符标记函数,函数内部可以使用递归。当一个函数用 tailrec 修饰符标记并满足所需的形式时,编译器会优化该递归,留下一个快速而高效的基于循环的版本。
要符合 tailrec 修饰符的条件的话,函数必须将其自身调用作为它执行的最后一个操作。在递归调用后有更多代码时,不能使用尾递归,并且不能用在 try/catch/finally 块中。


















![[02/27][官改] Simplicity@MIX2 ROM更新-一一网](https://www.proyy.com/wp-content/uploads/2020/02/3168457341.jpg)



![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)