自学内容网 自学内容网

Kotlin 2.1.0 入门教程(二)

函数

一个带有两个 Int 参数并返回 Int 类型的函数。

fun sum(a: Int, b: Int): Int {
    return a + b
}

函数体可以是一个表达式,其返回类型可以省略,省略会被自动推断。

fun sum(a: Int, b: Int) = a + b

fun sum(a: Int, b: Int): Int = a + b

一个不返回值的函数。

Unit 类型表示函数没有返回值。Unit 类似于 Java 中的 void,但它是 Kotlin 中的一个实际类型。如果函数返回 Unit,则可以省略 Unit 声明。

fun printSum(a: Int, b: Int): Unit {
    println("sum of $a and $b is ${a + b}")
}

fun printSum(a: Int, b: Int) {
    println("sum of $a and $b is ${a + b}")
}

在声明函数参数时,可以使用尾随逗号。

fun func(a: Int, b: Int,) = 0

默认参数

函数参数可以有默认值,当您跳过相应参数时会使用这些默认值。这可以减少重载函数的数量。

fun read(b: ByteArray, off: Int = 0, len: Int = b.size,) {}

重写方法时总是使用基类方法的默认参数值。当重写一个带有默认参数值的方法时,必须从签名中省略默认参数值。

open class A {
    open fun foo(i: Int = 10) {}
}

class B : A() {
    override fun foo(i: Int) {}  // 不允许设置默认值。
}

如果一个默认参数位于没有默认值的参数之前,则只能通过使用命名参数调用函数来使用默认值。

fun foo(bar: Int = 0, baz: Int,) {}

foo(baz = 1) // 使用了默认值 bar = 0。
fun main() {
    func(1, 2) // a = 1, b = 2
    func(b = 2) // a = -1, b = 2
    // func(1) // error
}

fun func(a: Int = -1, b: Int,) = println("a = $a, b = $b")

如果默认参数后面的最后一个参数是 lambda 表达式,您可以将其作为命名参数传递,或放在括号外。

fun main() {
foo { println("func1") } // a = 0, b = 1, func = func1
    foo(1) { println("func2") } // a = 1, b = 1, func = func2
    foo(1, 2) { println("func3") } // a = 1, b = 2, func = func3
    
foo(func = { println("func4") }) // a = 0, b = 1, func = func4
    foo(1, func = { println("func5") }) // a = 1, b = 1, func = func5
    foo(1, 2, func = { println("func6") }) // a = 1, b = 2, func = func6
    foo(1, 2, { println("func7") }) // a = 1, b = 2, func = func7
}

fun foo(a: Int = 0, b: Int = 1, func: () -> Unit,) {
    print("a = $a, b = $b, func = ")
    func()
}

命名参数

在调用函数时,可以为一个或多个参数命名。

当一个函数有许多参数且难以将值与参数关联时,这非常有用。

在函数调用中使用命名参数时,可以自由更改参数的顺序。如果想使用参数的默认值,可以直接省略这些参数。

fun main() {
foo(1)
    foo(a = 1)
    foo(1, c = false)
    foo(a = 1, c = false, b = true)
}

fun foo(a: Int, b: Boolean = true, c: Boolean = true) {}
fun main() {
foo(1)
    foo(2, c = 1)
    foo(3, c = 1, b = 2, d = 3)
    foo(4, 4, d = 5)
}

fun foo(a: Int, b: Int = 0, c: Int = 0, d: Int = 0) {}

可以使用展开运算符 * 传递带有名称的可变数量参数 vararg

fun foo(vararg strings: String) {}

foo(strings = *arrayOf("a", "b", "c"))

JVM 上调用 Java 函数时,不能使用命名参数语法,因为 Java 字节码并不总是保留函数参数的名称。

返回 Unit

如果一个函数不返回值,那么它的返回类型是 Unit

Unit 是一个只有一个值的类型:Unit,这个值不需要显式返回。

fun main() {
    func1()
    func2()
    func3()
    func4()
    func5()
}

fun func1(): Unit {
    return
}

fun func2(): Unit {
    return Unit
}

fun func3() {
    return
}

fun func4(): Unit {
}

fun func5() {
}

可变数量参数 vararg

可以使用 vararg 修饰符标记函数的参数(通常是最后一个参数)。

在这种情况下,可以向函数传递可变数量的参数。

fun <T> asList(vararg ts: T): List<T> {
    val result = ArrayList<T>()
    for (t in ts)
        result.add(t)
    return result
}

val list = asList(1, 2, 3)

在函数内部,类型为 Tvararg 参数 ts 被视为 T 的数组,如上例所示,其中 ts 变量的类型是 Array<out T>

只有一个参数可以被标记为 varargvararg 参数通常应放在参数列表的最后。

如果 vararg 参数不是列表中的最后一个参数,则可以使用命名参数语法为后续参数传递值,或者如果参数是函数类型,则可以在括号外传递 lambda 表达式。

fun printAll(prefix: String, vararg messages: String, suffix: String, func: () -> Unit) {
    println(prefix)
    for (message in messages) {
        println(message)
    }
    println(suffix)
    func()
}

fun main() {
    // Start
    // Hello
    // World
    // End
    // OK
    printAll("Start", "Hello", "World", suffix = "End") { println("OK") }
}

当调用 vararg 函数时,可以单独传递参数,例如 asList(1, 2, 3)

如果已经有一个数组并希望将其内容传递给函数,请使用展开运算符(在数组前加上 *)。

fun printAll(vararg messages: String) {
    for (message in messages) {
        println(message)
    }
}

fun main() {
    val messages = arrayOf("Hello", "World", "Kotlin")
    printAll(*messages)
}
val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)

如果想将基本类型数组传递给 vararg,需要使用 toTypedArray() 函数将其转换为常规(类型化)数组。

基本类型数组(如 IntArrayDoubleArray 等)不能直接传递给 vararg 参数,必须先转换为常规(类型化)数组。

fun <T> prints(vararg numbers: T) {
    for (number in numbers) {
        println(number)
    }
}

fun main() {
    val intArr = intArrayOf(1, 2, 3).toTypedArray()
    prints(*intArr)
}
fun <T> func1(vararg para: T) {
    println(para is Array<T>)
}

fun func2(vararg para: Int) {
    println(para is IntArray)
}

fun main() {
    val intArr = intArrayOf(1, 2, 3)
    func1(*intArr.toTypedArray()) // true
    func2(*intArr) // true
}

中缀表示法

使用 infix 关键字标记的函数可以通过中缀表示法调用(省略点和调用时的括号)。

infix 函数必须满足以下要求:

  • 它们必须是成员函数或扩展函数。

  • 它们必须只有一个参数。

  • 参数不能是可变数量参数 vararg,也不能有默认值。

infix fun Int.addOne(x: Int) = x + 1

fun main() {
    println(2 addOne 1) // 2
    println(2.addOne(1)) // 2
}

infix 函数调用的优先级低于算术运算符、类型转换和 rangeTo 运算符。以下表达式是等价的:

  • 1 addOne 2 + 3 等价于 1 addOne (2 + 3)

  • 0 until n * 2 等价于 0 until (n * 2)

  • xs union ys as Set<*> 等价于 xs union (ys as Set<*>)

另一方面,infix 函数调用的优先级高于布尔运算符 &&||isin 检查以及其他一些运算符。以下表达式也是等价的:

  • a && b xor c 等价于 a && (b xor c)

  • a xor b in c 等价于 (a xor b) in c

请注意,infix 函数始终需要指定接收者和参数。

当使用中缀表示法在当前接收者上调用方法时,请显式使用 this。这是为了确保解析的明确性。

class Foo {
    infix fun add(s: String) {
    }

    fun build() {
        this add "abc" // ok
        add("abc") // ok
        add "abc" // error
    }
}

函数作用域

Kotlin 函数可以在文件的顶层声明,这意味着不需要像在 Java 等语言中那样创建一个类来存放函数。

除了顶层函数外,Kotlin 函数还可以作为成员函数和扩展函数在局部声明。

// 顶层函数。
fun printMessage(message: String) {
    println(message)
}

class Example {
    // 成员函数。
    fun doSomething() {
        println("Doing something")
    }
}

// 扩展函数。
fun Example.printAdditionalInfo() {
    println("Additional info")
}

fun main() {
    printMessage("Hello, World!") // 调用顶层函数。
    val example = Example()
    example.doSomething() // 调用成员函数。
    example.printAdditionalInfo() // 调用扩展函数。
}

局部函数

Kotlin 支持局部函数,即函数内部定义的函数。

fun outerFunction() {
    println("outerFunction")

    // 局部函数。
    fun innerFunction() {
        println("innerFunction")
    }

    innerFunction() // 调用局部函数。
}

fun main() {
    // outerFunction
    // innerFunction
    outerFunction()
}

局部函数可以访问外部函数的局部变量(闭包)。

fun outerFunction() {
    var visited = false // 外部函数的局部变量。
    println("visited = $visited")

    fun innerFunction() {
        visited = true // 访问并修改外部函数的变量。
        println("visited = $visited")
    }

    innerFunction() // 调用局部函数。
    println("visited = $visited")
}

fun main() {
    // visited = false
    // visited = true
    // visited = true
    outerFunction()
}

尾递归函数

当一个函数被标记为 tailrec 并满足所需的形式条件时,编译器会优化递归,将其替换为快速且高效的基于循环的版本。

尾递归函数可以通过编译器优化为循环,从而避免递归调用导致的栈溢出问题。

tailrec fun factorial(n: Int, result: Int = 1): Int {
    return if (n == 1) result else factorial(n - 1, n * result)
}

fun main() {
    println(factorial(5)) // 120
}

要符合 tailrec 修饰符的条件,函数必须将调用自身作为其执行的最后一个操作。

在递归调用之后还有代码、在 try / catch / finally 块中或开放函数中,不能使用尾递归。

目前,KotlinJVMKotlin/Native 支持尾递归。


原文地址:https://blog.csdn.net/qq_19661207/article/details/145198936

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!