类与对象
函数
如一直使用的:
fun main() {
println("Hello word")
}
定义:完成特定任务或者功能的独立程序代码单位。函数可以将重复的代码封装,减少冗余
fun 函数名([参数1, 参数2, ...]): 返回值类型 {
// 函数体
}
例如:
fun test(): Unit {
println("PHP是世界上最好的语言.kt")
}
fun main() {
test()
}
Unit
表示没有返回值,类似于Java的void
,当函数返回值为Unit
时,可以省略
带有参数的函数:
fun add(a: Int, b: Int): Int {
return a + b
}
fun main() {
val result = add(1, 100)
println(result)
}
在定义函数时,小括号中填入的就是形参,代表调用时要传入的参数,在实际调用时,传入的参数就是实参,函数调用后,得到返回值,可以复制给变量或者作为其他函数参数
形参默认带有值的函数:
若在调用时不想传入参数值,可以给形参赋予一个默认值
fun success(title: String = "success") {
println(title)
}
fun main() {
success()
success("成功!")
}
也可以指定传入哪形参的值,哪一个用默认值:
fun add2(a: Int = 1, b: Int = 2): Int {
return a + b
}
fun main() {
println(add2(b=100))
}
若函数体内容比较简单,可以一行写下,可以省略花括号:
fun add(a: Int, b: Int): Int = a + b
还需要注意的是,函数的形参默认情况下为常量。无法修改:
嵌套函数:
在函数内部中也可以定义函数:
fun outer() {
println("outer")
var a = 100
fun inner() {
println("inner")
println(a)
a = 1000
fun innerInner() {
println("innerInner")
}
}
inner()
println(a)
}
在函数内部定义的函数作用于是受限的,只能在定义它的函数内使用,内部函数可以访问外部函数的变量。
我们不能同时编写多个同名函数,但当同名函数的参数不一致,返回值一致时,是允许的,这种情况称为函数重载,编译器可以根据调用时传入的参数来判断执行哪个同名函数:
fun testFun() {
fun test() {
println("test")
}
fun test(t: String) {
println(t)
}
test()
test("hello")
}
变量
前面所使用的都是在函数中的局部变量,我们也可以将变量的作用于提升:
var value: String = "test" // 跟定义函数一样,直接写在Kt文件中
fun testValue() = println(value)
声明变量的完整语法
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
对于这种顶层定义的变量(包括后面类中会用到的成员属性变量)可以具这两个可选的函数,它们本质上是一个get和set函数:
- getter:用于获取这个变量的值,默认情况下直接返回当前这个变量的值
- setter:用于修改这个变量的值,默认情况下直接对这个变量的值进行修改
我们在使用这种全局变量时,对于变量的获取和设定,本质上都是通过其getter和setter函数来完成的,只不过默认情况下不需要我们去编写。在默认情况下,变量的获取就是直接返回,设置就是直接修改,不过有些时候我们可能希望修改这些变量获取或修改时执行的操作,我们可以手动编写:
var a: Int = 5
get() {
println("a: $field")
return field * 2
}
set(value) {
println("我被赋值了.")
field = value
}
fun main() {
println(a)
a = 100
}
这里使用的field准确的说应该是Kotlin
提供的"后备字段",因为我们使用getter和setter本质上替代了原有的获取和修改方式,使其变得更像是函数的调用,因此,为了能够继续像之前使用一个变量那样去操作它本身,就有了这个后备字段。
注意在get中不能使用a,否则会出现递归调用,当变量用val修饰时,就没有set函数了,因为不可变,所以一些变量有时候也可以这样声明:
val b get() = 5
递归函数
递归函数就是在函数内调用自己, 当不加以限制时, 就会无限制的调用下去,最终到达一定深度, 强制终止, 抛出异常.
fun a() {
a()
}
fun main() {
a()
}
当我们对递归函数加以限制, 可以将某些功能代码简洁化.
著名的斐波拉契数列:
fun fib(n: Int): Int {
if (n <= 2) return 1
return fib(n - 1) + fib(n - 2)
}
不过,这种函数的效率就非常低了,相比循环来说,使用递归解决斐波那契问题,时间复杂度会呈指数倍增长,且n大于20时基本可以说很卡了(可以想象一下,每一个fib(n)都会分两个出去,实际上这个中间存在大量重复的计算)。
Kotlin
中提供了tailrec
关键字来优化这种将尾部作为返回值进行递归的操作:
tailrec fun sum(n: Int, pre: Int = 0): Int {
return if (n <= 0) pre else sum(n - 1, pre + n)
}
对于斐波拉契数列:
tailrec fun fib2(n: Int, pre: Int = 0, next: Int = 1): Int {
return if (n == 0) pre else fib2(n - 1, next, pre + next)
}
fun main() {
var start = System.currentTimeMillis()
println(fib(40))
var end = System.currentTimeMillis()
println("cost: ${end - start}ms")
start = System.currentTimeMillis()
println(fib2(40))
end = System.currentTimeMillis()
println("cost: ${end - start}ms")
}
高阶函数
函数类型
要声明函数类型,需要按照以下规则:
- 所有函数类型都有一个括号,并在括号中填写参数类型列表和一个返回类型,比如:
(A, B) -> C
表示一个函数类型,该类型表示接受类型A
和B
的两个参数并返回类型C
的值的函数。参数类型列表可为空的,比如() -> A
,注意,即使是Unit
返回类型也不能省略。
fun main() {
var func1: (Int) -> Int
var func2: (Int) -> Int
}
函数类型的变量,可以当做一个普通的函数使用:
fun test(func: (String) -> Int) {
func("haha")
}
也可以套娃:
var func: (Int) -> ((String) -> Long)
为函数类型起一个别名:
typealias func1 = (String) -> Long
fun main() {
var f3: func1
}
仅仅有了函数类型,具体功能是怎么实现呢?前面都是通过fun
声明函数,现在函数类型变量也可以使用这个函数:
fun test(s: String): Int {
println(s)
return 6
}
fun main() {
var func: (String) -> Int = ::test
}
使用::
引用一个现成的函数,引用的函数需要和变量的函数类型一致。除了这种,还可以使用匿名函数,如其名,也就是没有名字的函数。
fun main() {
var func2: (String) -> Int = fun(s: String): Int {
println("s: $s")
return if (s.length > 2) 2 else 1
}
}
第三种就是Kotlin
提供的Lambda表达式
:
Lambda表达式只需要在{}
中编写函数体即可,默认情况下,如果函数只有一个参数,可以使用it
代表传入的参数,而不需要显示声明
fun main() {
var func3: (String) -> Int = {
println("s: $it")
1 // 与if情况一致,最后一行为返回值
}
}
当有多个参数时,可以这样编写:
fun main() {
var func4: (String, String) -> String = { a, b ->
println("a: $a, b: $b")
a + b
}
}
调用高阶函数:
直接当做普通函数调用即可:
fun main() {
var func4: (String, String) -> String = {a, b ->
println("a: $a, b: $b")
a + b
}
func4("hello", "word")
}
将Lambda作为参数传递:
fun foo(num: Int, wrap: (Int) -> Int) {
println(wrap(num))
}
fun main() {
foo(2, {a: Int -> a * 100})
println({ s: String -> s.uppercase() } ("hello"))
}
当函数最后最后一个形参时函数类型,传参时可以直接写在括号后面:
foo(2) { a: Int -> a * 100 }
这种语法也被称为 尾随lambda表达式,能省的东西都省了,不过只有在最后一个参数是函数类型的情况下才可以,如果不是最后一位,就没办法做到尾随了。
如果函数就没有其他参数了,能把()
也给省了,简洁的雅痞
fun bar(func: (Int) -> String) {
func(6666)
}
fun main() {
val a = bar { "收到的参数: $it" }
println(a)
}
最后特别需要注意的是,在Lambda中无法直接使用return
返回结果,默认返回结果是最后一行,如果需要提前返回结果,需要用到控制流程中提到的标签:
fun main() {
var func1: (Int) -> String = tag@{
if (it > 100) return@tag "我是提前返回的"
println("我是正常的")
"收到的参数: $it"
}
println(func1(20))
}
如果是尾随Lambda表达式,默认的标签就是函数名称:
fun bar(func: (Int) -> String) {
println(func(6666))
}
bar { if (it > 100) return@bar "hah" else "hih" }
内联函数
使用高阶函数会可能会影响运行时的性能:每个函数都是一个对象,而且函数内可以访问一些局部变量,这可能会存在内存分配,用于对象和类,以及虚拟调用时造成额外开销。
为了优化性能,开销可以通过内联Lambda表达式来消除。使用inline
关键字会影响函数本身和传递给它的Lambda,它能够让方法的调用在编译时,直接替换为方法的执行代码:
inline fun testFunc() {
println("内联函数")
println("内联函数")
println("内联函数")
}
fun main() {
test()
}
反编译后的代码:
如果是高阶函数,效果更好:
inline fun testFunc2(f: (String) -> Unit) {
println("这是一个内联函数")
f("haha")
}
fun main() {
testFunc2 { println("传入的参数: $it") }
}
内联会导致编译出来的代码变多,但是同样的换来了性能上的提升,不过这种操作仅对于高阶函数有显著效果,普通函数实际上完全没有内联的必要,也提升不了多少性能。
内联函数中的函数类型形参,无法再作为值,赋值给变量,只能调用,当不希望某个函数类型形参内联时,可以使用noinline
修饰:
fun main() {
test({ println("我是一号:$it") }, { println("我是二号:$it") })
}
//在不需要内联的函数形参上添加noinline关键字,来防止此函数的调用内联
inline fun test(func: (String) -> Unit, noinline func2: (Int) -> Unit){
println("这是一个内联函数")
func("HelloWorld")
var a = func2 //这样就不会报错,但是不会内联了
func2(666)
}
由于内联函数搬运代码的特性,所以Lambda中的return语句可以不带标签,但这样会导致直接返回:
fun main() {
test { return } // 内联高阶函数的Lambda参数可以直接写return不指定标签
println("调用上面方法之后")
}
inline fun test(func: (String) -> Unit){
func("HelloWorld")
println("调用内联函数之后")
}
上述代码运行后,两个println
都不会打印,这种情况称为非局部返回:
反编译后的Java代码:
在Kotlin中Lambda表达式支持一个叫做"标签返回"(labeled return)的特性,这使得你能够从一个Lambda表达式中返回一个值给外围函数,而不是简单地返回给Lambda表达式所在的最近的封闭函数:
fun main() {
test { return@main } //标签可以直接指定为外层函数名称main来提前终止整个外部函数
println("调用上面方法之后")
}
inline fun test(func: (String) -> Unit){
func("HelloWorld")
println("调用内联函数之后")
}
执行结果与上面是一致的,为了避免这种情况,可以将标签写为@test
:
fun main() {
test { return@test } //这样就只会使test返回,而不会影响到外部函数了
println("调用上面方法之后")
}