【文字内容源于《疯狂Kotlin讲义》,代码内容原创】
目录
一、函数入门
1.定义和调用函数
Kotlin 声明函数必须使用 fun 关键字。函数语法格式的详细说明如下。
(1)函数名:从语法的角度来看,函数名只要是 个合法的标识符即可;从程序可读性的角度来看,函数名应该由 个或多个有意义的单词连缀而成,第1个单词首字母小写,后面每个单词首字母大写,其他字母全部小写,单词与单词之间不需要使用任何分隔符。
(2)返回值类型: 返回值类型可以是 Kotlin 语言所允许的任何数据类型,如果声明了函数返回值类型,则函数体内应该有1条有效的 return 语句,该语句返回1个变量或1个表达式,这个变量或者表达式的类型必须与此处声明的类型匹配。
如果希望声明一个函数没有返回值,则有如下两种声明方式。
(a)省略“:返回值类型”部分
(b)使用“: Unit ”指定返回 Unit 表没有返回值
(3)形参列表: 形参列表用于定义该函数可以接受的参数,形参列表由零组到多组“形参名:参数类型”组合而成,多组参数之间以英文逗号(,)隔开,形参名和形参类型之间以英文冒号隔开。 旦在定义函数时指定了形参列表 那么在调用该函数时就必须传入对应的参数值一一谁调用函数,谁负责为形参赋值。
private fun printStr(str: String) {
println(str)
}
private fun getStr(index :Int):String{
val str = "this is $index"
println(str)
return str
}
调用方法:
printStr("函数入门...")
getStr(2022)
运行结果:
函数入门...
this is 2022
如果为函数声明了返回值类型,则应该在函数体中使用 return 语句显式返回一个值, return
语句返回的值既可是常量,也可是有值的变量,还可是一个表达式。
2.函数返回值和Unit
private fun emptyReturnType1(){
println("this is emptyReturnType1 func")
}
private fun emptyReturnType2(index:Int) : Unit{
val indexStr = "index is $index"
println(indexStr)
}
调用方法:
emptyReturnType1()
emptyReturnType2(24)
运行结果:
this is emptyReturnType1 func
index is 24
3.递归函数
在一个函数体内调用它自身被称为函数递归。函数递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。
private fun recursive(a: Int) {
if (a >= 10) {
println("递归继续...$a")
recursive(a-1)
} else {
println("递归结束!!!")
}
}
调用函数:
recursive(16)
运行结果:
递归继续...16
递归继续...15
递归继续...14
递归继续...13
递归继续...12
递归继续...11
递归继续...10
递归结束!!!
在定义递归函数时有一条最重要的规定 递归一定要向己知方向进行。
4.单表达函数
在某些情况下,函数只是返回单个表达式,此时可以省略花括号井在等号(=)后指定函数体即可。这种方式被称为单表达式函数。
private fun singleExpr1(a: Int) = println("a is $a")
private fun singleExpr2(a: Int, b: Int) = ((a + b) * (a - b)).also {
println("output = ${it.toString()}")
}
调用函数:
singleExpr1(614)
singleExpr2(14, 3)
运行结果:
a is 614
output = 187
对于单表达式函数而言,编译器可以推断出函数的返回值类型,因此 Kotlin 允许省略声明函数的返回值类型。
二、函数的形参
1.命名参数
Kotlin 函数的参数名不是无意义的,Kotlin 许调用函数时通过名字来传入参数值。因此,Kotlin 函数的参数名应该具有更好的语义一一程序可以立刻明确传入函数的每个参数的含义。
Kotlin 函数除第一个参数之外,其他所有形参都分配隐式的外部形参名一一这些外部形参名与内部形参名保持一致。
private fun nameParam(color: String, food: String) {
println("$food 是 $color 颜色的")
}
调用方法:
nameParam("白", "馒头")
nameParam(color = "紫", food = "紫薯")
nameParam(food = "橙子", color = "橘")
运行结果:
馒头 是 白 颜色的
紫薯 是 紫 颜色的
橙子 是 橘 颜色的
调用该函数时,既可使用传统的根据位置参数来调用,也可根据命名参数来调用,并可交换参数的位置,还可混合使用命名参数和位置参数。
需要说明的是,如果希望调用函数时混合使用命名参数和位置参数,那么命名参数必须位于位置参数之后。换句话说,在命名参数之后的只能是命名参数。
2.形参默认值
在某些情况下,程序需要在定义函数时为一个或多个形参指定默认值一一这样调用函数时就可以省略该形参,而直接使用该形参的默认值。
private fun defaultParam(date: String, des: String = "天气好") {
println("$date $des")
}
调用方法:
defaultParam("2022-03-03", "天气不好")
defaultParam("2022-03-03") //未指定参数“des”值,使用默认值
运行结果:
2022-03-03 天气不好
2022-03-03 天气好
通常建议将带默认值的参数定义在形参列表的最后。
3.个数可变的形参
Kotlin 允许定义个数可变的参数,从而允许为函数指定数量不确定的形参。如果在定义函数时,在形参的类型前添加 vararg修饰,则表明该形参可以接受多个参数值,多个参数值被当成数组传入。
private fun varargs(vararg index: Int) {
print("index has ")
for (i in index){
print("$i ")
}
println()
}
调用方法:
varargs(1, 2, 3)
varargs(2022, 3, 3)
运行结果:
index has 1 2 3
index has 2022 3 3
三、函数重载
与Java 类似,Kotlin 许定义多个同名函数,只要形参列表或返回值类型不同就行。如果程序包含了两个或两个以上函数名相同,但形参列表不同的函数,就被称为函数重载。
与Java 类似的是, Kotlin 的函数重载也只能通过形参列表进行区分,形参个数不同、形参类型不同都可以算函数重载。但仅有形参名不同、返回值类型不同或修饰符不同,则不能算函数重载。
private fun sayHi() {
println("hello 你好啊")
}
private fun sayHi(name: String) {
println("hello 你好啊, 我是${name}啦")
}
调用方法:
sayHi()
sayHi("桃子不出")
运行结果:
hello 你好啊
hello 你好啊, 我是桃子不出啦
四、局部函数
Kotlin 还支持在函数体内部定义函数,这种被放在函数体内定义的函数称为局部函数。在默认情况下,局部函数对外部是隐藏的,局部函数只能在其封闭( enclosing )函数内有效,其封闭函数也可以返回局部函数,以便程序在其他作用域中使用局部函数。
private fun localFunc(type: String, str: String) {
fun printStr() {
println("str is $str")
}
fun strLength() {
println("str.length is ${str.length}")
}
fun strNothing() {
println("Nothing need to do")
}
when (type) {
"PRINT" -> printStr()
"LENGTH" -> strLength()
else -> strNothing()
}
}
调用方法:
localFunc("PRINT","桃子不出")
localFunc("LENGTH","桃子不出")
localFunc("","桃子不出")
运行结果:
str is 桃子不出
str.length is 4
Nothing need to do
五、高阶函数
1.使用函数类型
Kotlin 的每个函数都有特定的类型,函数类型由函数的形参列表、 和返回值类型组成。
private fun useFuncType() {
val funcA: (Int) -> Int
val funcB: (Int, Int) -> String
fun double(i: Int): Int {
return (i * i).apply {
println("double value is $this")
}
}
fun printWidthAndHeight(w: Int, h: Int): String {
return "width = $w, height = $h".also {
println(it)
}
}
funcA = ::double
funcB = ::printWidthAndHeight
funcA(12)
funcB(3, 4)
}
调用方法:
useFuncType()
运行结果:
double value is 144
width = 3, height = 4
当直接访问一个函数的函数引用,而不是调用函数时,需要在函数名前添加两个冒号,而且不能在函数后面添加圆括号一一 旦添加圆括号,就变成了调用函数,而不是访问函数引用。
2.使用函数类型作为形参类型
有时候需要定义一个函数 该函数的大部分计算逻辑都能确定,但某些处理逻辑暂时无法确定一一这意味着某些程序代码需要动态改变,如果希望调用函数时能动态传入这些代码,就需要在函数中定义函数类型的形参,这样即可在调用该函数时传入不同的函数作为参数,从而动态改变这些代码。
private fun useFuncTypeForParamType(food: String, fn: (String) -> Unit) {
println("food is $food")
fn(food)
}
private fun printFruit(fruit: String) {
println("今天的水果是$fruit")
}
private fun printJuice(juice: String) {
println("今天的果汁是$juice")
}
调用方法:
useFuncTypeForParamType("苹果", ::printFruit)
useFuncTypeForParamType("橙汁", ::printJuice)
运行结果:
food is 苹果
今天的水果是苹果
food is 橙汁
今天的果汁是橙汁
3.使用函数类型作为返回值类型
private fun useFuncTypeForReturnValue(index: Int): () -> String {
fun getFirstIndexDes() = "index = 0,初始位置..."
fun getOtherIndexDes() = "index = $index, 继续执行..."
return when (index) {
0 -> ::getFirstIndexDes
else -> ::getOtherIndexDes
}
}
六、局部函数与Lambda表达式
1.使用Lambda表达式代替局部函数
定义 Lambda 表达式与局部函数只是存在如下区别。
(1)Lambda 表达式总是被大括号括着
(2)定义 Lambda 表达式不需要 fun 关键字,无须指定函数名。
(3)形参列表(如果有的话)在->之前声明,参数类型可以省略。
(4)函数体(Lambda 表达式执行体)放在 ->之后。
(5)函数的最后一个表达式自动被作为 Lambda表达式的返回值,无须使用 return 关键字。
private fun lambdaTest1Before(type: String): (Int) -> Int {
fun test1(a: Int): Int {
return a * 2
}
fun test2(a: Int): Int {
return a * a
}
return when (type) {
"Test1" -> ::test1
else -> ::test2
}
}
private fun lambdaTest1After(type: String): (Int) -> Int {
return when (type) {
"Test1" -> { a: Int -> a * 2 }
else -> { a: Int -> a * a }
}
}
调用方法:
private fun lambdaTest1() {
val testBefore1 = lambdaTest1Before("Test1")
val testBefore2 = lambdaTest1Before("Test2")
val testAfter1 = lambdaTest1After("Test1")
val testAfter2 = lambdaTest1After("Test2")
val testBeforeValue1 = testBefore1(35)
val testBeforeValue2 = testBefore2(67)
val testAfterValue1 = testAfter1(35)
val testAfterValue2 = testAfter2(67)
println("局部函数调用...testBeforeValue1 = $testBeforeValue1, testBeforeValue2 = $testBeforeValue2")
println("lambda表达式调用...testAfterValue1 = $testAfterValue1, testAfterValue2 = $testAfterValue2")
}
lambdaTest1()
运行结果:
局部函数调用...testBeforeValue1 = 70, testBeforeValue2 = 4489
lambda表达式调用...testAfterValue1 = 70, testAfterValue2 = 4489
2.Lambda表达式的脱离
作为函数参数传入的 Lambda 表达式可以脱离函数独立使用。
private fun lambdaTest2() {
val lambda1 = { s1: String, s2: String -> println("$s1 有 $s2") }
val lists = mapOf<String, String>("书柜" to "书", "动物园" to "猴子", "水果店" to "苹果")
for ((key, value) in lists) {
println(lambda1(key, value))
}
}
调用方法:
lambdaTest2()
运行结果;
书柜 有 书
kotlin.Unit
动物园 有 猴子
kotlin.Unit
水果店 有 苹果
kotlin.Unit
七、Lambda表达式
1.调用Lambda表达式
Lambda 表达式的本质是功能更灵活的代码块,因此完全可以将 Lambd 表达式赋值给变量或直接调用 Lambda 表达式。
private fun lambdaTest3() {
val lambda1 = { a: Int, b: Int -> a + b }
println(lambda1(21, 2))
println(lambda1(89, 77))
}
调用方法:
lambdaTest3()
运行结果:
23
166
2.利用上下文推断类型
完整的 Lambda 表达式需要定义形参类型,但是如果 kotlin 可以根据 Lambda 表达式上下文推断出形参类型,那么 Lambda 表达式就可以省略形参类型。
val lambda1: (Int, Int) -> Int = { a, b -> a + b }
3.省略形参名
Lambda 表达式不仅可以省略形参类型,而且如果只有一个形参,那么 kotlin 允许省略Lambda 表达式的形参名。如果 Lambda 表达式省略了形参名,那么此时 ->也不需要了, Lambda 表达式可通过 it 来代表形参。
val lambda2: (Int) -> String = { "It's $it" }
val lambda3: (String) -> Int = { it.length }
4.调用Lambda表达式的约定
Kotlin 语言有一个约定:如果函数的最后一个参数是函数类型,而且你打算传入Lambda出表达式作为相应的参数,那么就允许在圆括号之外指定 Lambda 表达式。
private fun lambdaTest4(a: Int, func: (Int) -> String) {
println("hello -- ${func(a)}")
}
调用方法:
//约定前
lambdaTest4(23, { "It's $it"})
//约定后
lambdaTest4(23) { "It's $it" }
运行结果:
hello -- It's 23
5. 个数可变的参数和Lambda参数
如果一个函数既包含个数可变的形参, 也包含函数类型的形参,那么就应该将函数类型的形参放在最后。
private fun lambdaTest5(vararg str: String, func: (String) -> String) {
for (s in str) {
println("${s}.size = ${func(s)}")
}
}
调用方法:
lambdaTest5("test", "banana", "computer", "watter") { "${it.length}" }
运行结果:
test.size = 4
banana.size = 6
computer.size = 8
watter.size = 6
八、匿名函数
1.匿名函数的用法
匿名函数与普通函数基本相似,只要将普通函数的函数名去掉就变成了匿名函数。
private fun anonymousFunc1() {
//匿名函数
val func1 = fun(str: String): String {
return "func1 $str length is ${str.length}"
}
//lambda表达式
val func2 = { str: String -> "func2 $str length is ${str.length}" }
println(func1("hello word"))
println(func2("hello word yep"))
}
调用方法:
anonymousFunc1()
运行结果:
func1 hello word length is 10
func2 hello word yep length is 14