Kotlin函数全解析

文章目录

  • 1.函数定义
  • 2.函数参数
    • 2.1 默认参数
    • 2.2 具名参数
  • 3.各种函数
    • 3.1 返回 Unit 的函数
    • 3.2 单表达式函数
    • 3.3 可变数量参数
    • 3.4 中缀函数
    • 3.5 局部函数
    • 3.6 成员函数
    • 3.7 泛型函数
    • 3.8 尾递归函数
    • 3.9 高阶函数
      • 函数类型
      • 函数实例化
      • 函数类型实例调用
    • 3.10 内联函数
    • 3.11 匿名函数与 Lambda 表达式
      • Lambda 表达式语法
      • 传递末尾的 lambda 表达式
      • it:单个参数的隐式名称
      • 从 lambda 表达式中返回一个值
      • LINQ-风格
      • 下划线用于未使用的变量
      • 匿名函数
      • 闭包
      • 带有接收者的函数字面值
    • 3.12 操作符重载

同Java完全面向对象的规则不太一样,在Kotlin的世界里,函数也是准C位的,同面向对象一样属于一等公民,Kotlin也提倡函数式编程。

1.函数定义

标准函数的定义通常如下属示例代码:

fun double(x: Int): Int {
    return 2 * x
}

调用方式:

val result = double(2)

调用成员函数使用点表示法:

Stream().read() // 创建类 Stream 实例并调用 read()

函数参数形式:
name: type

fun powerOf(number: Int, exponent: Int): Int { /*……*/ }

函数作用域
Kotlin 函数可以在文件顶层声明,这意味着你不需要像一些语言如 Java、C# 与 Scala 那样需要创建一个类来保存一个函数。此外除了顶层函数,Kotlin 中函数也可以声明在局部作用域、作为成员函数以及扩展函数。

2.函数参数

2.1 默认参数

fun read(
    b: ByteArray,
    off: Int = 0,
    len: Int = b.size,
) { /*……*/ }
  • 调用时省略参数将使用默认参数
  • 默认参数可以减少方法重载
  • override一个有默认参数值的方法时,必须从签名中省略默认参数值
  • 如果一个默认参数在一个无默认值的参数之前,那么该默认值只能通过使用具名参数调用该函数来使用
  • 如果在默认参数之后的最后一个参数是 lambda 表达式,那么它既可以作为具名参数在括号内传入,也可以在括号外传入

2.2 具名参数

调用时候,携带函数变量名,声明时候可赋默认值
在Compose UI 库中设置UI属性大量使用具名参数,使用具名参数可以使参数调用一目了然

fun somemethod(
    str: String,
    normalizeCase: Boolean = true,
    wordSeparator: Char = ' ',
) { /*……*/ }

调用时

reformat(
    "String!",
   normalizeCase = false,
)

3.各种函数

在Kotlin中定义了多种类型的函数

3.1 返回 Unit 的函数

Unit函数和Java中无返回值的函数类似,Unit有点类似Void, Unit可选

fun printHello(name: String?) { …… }

3.2 单表达式函数

当函数返回单个表达式时,可以省略花括号并且在 = 符号之后指定代码体即可:

fun double(x: Int): Int = x * 2

当返回值类型可由编译器推断时,显式声明返回类型是可选的:

fun double(x: Int) = x * 2

3.3 可变数量参数

函数的参数(通常是最后一个)可以用 vararg 修饰符标记, java中也有类似类型

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

在本例中,可以将可变数量的参数传递给函数:

val list = asList(1, 2, 3)

在函数内部,类型 T 的 vararg 参数的可见方式是作为 T 数组,如上例中的 ts 变量具有类型 Array

3.4 中缀函数

标有 infix 关键字的函数也可以使用中缀表示法(忽略该调用的点与圆括号)调用。中缀函数满足以下条件:

  • 它们必须是成员函数或扩展函数。
  • 它们必须只有一个参数。
  • 其参数不得接受可变数量的参数且不能有默认值。

示例如下:

infix fun Int.shl(x: Int): Int { …… }

// 用中缀表示法调用该函数
1 shl 2

// 等同于这样
1.shl(2)

中缀函数调用的优先级低于算术操作符、类型转换以及 rangeTo 操作符。

  • 1 shl 2 + 3 等价于 1 shl (2 + 3)
    另一方面,中缀函数调用的优先级高于布尔操作符 && 与 ||、is- 与 in- 检测以及其他一些操作符。
  • a && b xor c 等价于 a && (b xor c)
  • a xor b in c 等价于 (a xor b) in c

3.5 局部函数

Kotlin 支持局部函数,即一个函数在另一个函数内部。

fun dfs(graph: Graph) {
    fun dfs(current: Vertex, visited: MutableSet<Vertex>) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
            dfs(v, visited)
    }

    dfs(graph.vertices[0], HashSet())
}

局部函数可以访问外部函数(闭包)的局部变量。在上例中,visited 可以是局部变量:

fun dfs(graph: Graph) {
    val visited = HashSet<Vertex>()
    fun dfs(current: Vertex) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
            dfs(v)
    }

    dfs(graph.vertices[0])
}

3.6 成员函数

成员函数是在类或对象内部定义的函数。

3.7 泛型函数

函数可以有泛型参数,通过在函数名前使用尖括号指定:

fun <T> singletonList(item: T): List<T> { /*……*/ }

3.8 尾递归函数

Kotlin 支持一种称为尾递归的函数式编程风格。

val eps = 1E-10 // "good enough", could be 10^-15

tailrec fun findFixPoint(x: Double = 1.0): Double =
    if (Math.abs(x - Math.cos(x)) < eps) x else findFixPoint(Math.cos(x))

传统写法

val eps = 1E-10 // "good enough", could be 10^-15

private fun findFixPoint(): Double {
    var x = 1.0
    while (true) {
        val y = Math.cos(x)
        if (Math.abs(x - y) < eps) return x
        x = Math.cos(x)
    }
}

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

3.9 高阶函数

高阶函数是将函数用作参数或返回值的函数。通常使用lambda表达式方式。高阶函数的一个不错的示例是集合的函数式风格的 fold), 它接受一个初始累积值与一个接合函数,并通过将当前累积值与每个集合元素连续接合起来代入累积值来构建返回值:

fun <T, R> Collection<T>.fold(
    initial: R, 
    combine: (acc: R, nextElement: T) -> R
): R {
    var accumulator: R = initial
    for (element: T in this) {
        accumulator = combine(accumulator, element)
    }
    return accumulator
}

参数 combine 具有函数类型 (R, T) -> R,因此 fold 接受一个函数作为参数, 该函数接受类型分别为 R 与 T 的两个参数并返回一个 R 类型的值。 在 for 循环内部调用该函数,然后将其返回值赋值给 accumulator
调用示例:

fun main() {
    //sampleStart
    val items = listOf(1, 2, 3, 4, 5)

    // Lambdas 表达式是花括号括起来的代码块。
    items.fold(0, { 
        // 如果一个 lambda 表达式有参数,前面是参数,后跟“->”
        acc: Int, i: Int -> 
        print("acc = $acc, i = $i, ") 
        val result = acc + i
        println("result = $result")
        // lambda 表达式中的最后一个表达式是返回值:
        result
    })

    // lambda 表达式的参数类型是可选的,如果能够推断出来的话:
    val joinedToString = items.fold("Elements:", { acc, i -> acc + " " + i })

    // 函数引用也可以用于高阶函数调用:
    val product = items.fold(1, Int::times)
    //sampleEnd
    println("joinedToString = $joinedToString")
    println("product = $product")
}

函数类型

Kotlin 使用类似 (Int) -> String 的函数类型来处理函数的声明
声明如下:

val onClick: () -> Unit = ……

多参数函数(A, B) -> C

带有接收者函数 A.(B) -> C

挂起函数 它的表示法中有一个 suspend 修饰符suspend () -> Unit 或者 suspend A.(B) -> C

函数类型表示法可以选择性地包含函数的参数名:(x: Int, y: Int) -> Point,用于表明参数含义。

函数别名 typealias ClickHandler = (Button, ClickEvent) -> Unit

函数实例化

函数实例化有以下几种方法:

  • 使用函数字面值的代码块
  • 使用已有声明的可调用引用
  • 使用实现函数类型接口的自定义类的实例

函数类型实例调用

函数类型的值可以通过其 invoke(……) 操作符调用: f.invoke(x) 或者直接 f(x)

如果该值具有接收者类型,那么应该将接收者对象作为第一个参数传递。 调用带有接收者的函数类型值的另一个方式是在其前面加上接收者对象, 就好比该值是一个扩展函数:1.foo(2)

3.10 内联函数

有时使用内联函数可以为高阶函数提供灵活的控制流。内联函数可以提高代码效率,同C++中内联函数一样,编译时代码直接插入调用位置。

Kotlin中使用的高阶函数会有定的性能开销,为了消除这种开销,Kotlin引入内联的概念,让编译器在编译时做代码优化。

内联函数使用inline修饰
inline fun lock(lock: Lock, body: () -> T): T { …… }
如果不希望内联所有传给内联函数的 lambda 表达式参数都内联,那么可以用 noinline 修饰符标记不希望内联的函数参数:
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { …… }

3.11 匿名函数与 Lambda 表达式

lambda 表达式与匿名函数是函数字面值,函数字面值即没有声明而是立即做为表达式传递的函数。考虑下面的例子:

max(strings, { a, b -> a.length < b.length })

函数 max 是一个高阶函数,因为它接受一个函数作为第二个参数。 其第二个参数是一个表达式,它本身是一个函数,称为函数字面值,它等价于以下具名函数:

fun compare(a: String, b: String): Boolean = a.length < b.length

Lambda 表达式语法

val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
简化形式
val sum = { x: Int, y: Int -> x + y }

传递末尾的 lambda 表达式

如果函数的最后一个参数是函数,那么作为相应参数传入的 lambda 表达式可以放在圆括号之外
val product = items.fold(1) { acc, e -> acc * e }
这种语法也称为拖尾 lambda 表达式。
如果该 lambda 表达式是调用时唯一的参数,那么圆括号可以完全省略:
run { println("...") }

it:单个参数的隐式名称

一个 lambda 表达式只有一个参数很常见。

ints.filter { it > 0 } // 这个字面值是“(it: Int) -> Boolean”类型的

从 lambda 表达式中返回一个值

可以使用限定的返回语法从 lambda 显式返回一个值。 否则,将隐式返回最后一个表达式的值。
下面两段代码等价:

ints.filter {
    val shouldFilter = it > 0
    shouldFilter
}
ints.filter {
    val shouldFilter = it > 0
    return@filter shouldFilter
}

LINQ-风格

这一约定连同在圆括号外传递 lambda 表达式一起支持 LINQ-风格) 的代码:
strings.filter { it.length == 5 }.sortedBy { it }.map { it.uppercase() }

下划线用于未使用的变量

map.forEach { _, value -> println("$value!") }

匿名函数

通常函数返回值可以类型推导出来,如果确实需要显式指出,那么可以使用Lamba表达式。

fun(x: Int, y: Int): Int = x + y

除了表达式还可以是代码块

fun(x: Int, y: Int): Int {
    return x + y
}

匿名函数与Lambda表达式区别:

  • 表达式形式支持类型推导,但是作为代码块时必须指明返回类型
  • lambda表达式返回带有return标签,而匿名表达式返回值必须为调用位置

闭包

Lambda 表达式或者匿名函数(以及局部函数和对象表达式) 可以访问其闭包 ,其中包含在外部作用域中声明的变量。 在 lambda 表达式中可以修改闭包中捕获的变量:

var sum = 0
ints.filter { it > 0 }.forEach {
    sum += it
}
print(sum)

带有接收者的函数字面值

有点类似扩展函数,使用this操作接收者对象。
当接收者类型可以从上下文推断时,lambda 表达式可以用作带接收者的函数字面值。

class HTML {
  fun body() { …… }
}

fun html(init: HTML.() -> Unit): HTML {
  val html = HTML()  // 创建接收者对象
  html.init()        // 将该接收者对象传给该 lambda
  return html
}

html {       // 带接收者的 lambda 由此开始
  body()   // 调用该接收者对象的一个方法
}

3.12 操作符重载

在 Kotlin 中可以为类型提供预定义的一组操作符的自定义实现。这些操作符具有预定义的符号表示(如 + 或 *)与优先级。为了实现这样的操作符,需要为相应的类型提供一个指定名称的成员函数或扩展函数。这个类型会成为二元操作符左侧的类型及一元操作符的参数类型。

具体参考官方手册之运算符重载章节。

你可能感兴趣的:(Kotlin基础,kotlin,android)