函数-Kotlin

39 阅读4分钟

0、函数作用域

顶层函数

Kotlin 函数可以在文件顶层声明,这意味着你不需要像一些语言如 Java、C# 那样需要创建一个来保存一个函数。

package com.mumu.mumu.kt

fun Int.realNameDouble2(): Int {
    return this * this
}

局部函数

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

fun realNameDouble(x: Int): Int {
    fun doubleAndDouble(x: Int): Int {
        return x * x
    }
    return doubleAndDouble(x) * doubleAndDouble(x)
}

成员函数

成员函数就是在类或对象内部定义的函数,平常用的最多就是成员函数

一、单表达式函数

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

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

fun max(x: Int, y: Int): Int = if (x >= y) x else y

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

fun double(x: Int) = x * 2

具有块代码体的函数必须始终显式指定返回类型,除非函数的返回类型为Unit

二、中缀函数

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

  • 必须是成员函数或扩展函数
  • 必须只有一个参数
  • 其参数不能是可变数量参数(varargs关键字)且不能有默认值
public infix fun shl(bitCount: Int): Int

public infix fun Int.until(to: Int): IntRange {
    if (to <= Int.MIN_VALUE) return IntRange.EMPTY
    return this .. (to - 1).toInt()
}

fun main() 
    1 shl 2
    val intRange = 1 until 10    
}

中缀函数调用的优先级低于算术操作符;类型转换以及rangeTo操作符。但是高于布尔操作符||&&is-in-检测以及其他一些操作符。以下表达式是等价的

  • 1 shl 2+3 等价于 1 shl (2+3)
  • a xor b in c 等价于(a xor b) in c

三、lmabda表达式和匿名函数

Lambda表达式和匿名函数

四、高阶函数

高阶函数

五、扩展函数

声明一个扩展函数需用一个接收者类型也就是被扩展的类型来作为他的前缀。 下面代码为 MutableList<Int> 添加一个 swap 函数:

fun MutableList<Int>.swap(index1: Int, index2: Int) { 
		val tmp = this[index1] // “this”对应该列表 
		this[index1] = this[index2]
		this[index2] = tmp
}

这个 this 关键字在扩展函数内部对应到接收者对象(传过来的在点符号前的对象) 现在, 可以对任意 MutableList<Int> 调用该函数了:

val list = mutableListOf(1, 2, 3)
list.swap(0, 2) // “swap()”内部的“this”会保存“list”的值

这个函数对任何 MutableList<T> 起作用,可以泛化它:

fun <T> MutableList<T>.swap(index1: Int, index2: Int) { 
		val tmp = this[index1] // “this”对应该列表 
		this[index1] = this[index2]
		this[index2] = tmp
}

扩展属性

与扩展函数类似,Kotlin 支持扩展属性:

(由于扩展没有实际的将成员插入类中扩展属性不能有初始化器)

val <T> List<T>.lastIndex: Int
    get() = size - 1
val House.number = 1 // 错误:扩展属性不能有初始化器

六、内联函数

使用高阶函数会带来一些运行时的效率损失:每一个函数都是一个对象,并且会捕获一个闭包。闭包那些函数体内会访问到的变量的作用域。内存分配(对于函数对象和类)和虚拟调用会引入运行时开销。

可以通过内联函数化lambdab表达式消除这类开销。

fun calculate(x: Int, cal: (Int) -> Int): Int {
    return cal(x)
}

inline fun calculate3(x: Int, cal: (Int) -> Int): Int {
    return cal(x)
}

fun main() {
    val value1 = calculate(100) { x -> x + x }
    val value2 = calculate3(100) { x -> x + x }
}

反编译之后:

public final void main() {
      int value1 = this.calculate(100, (Function1)null.INSTANCE);
      int x$iv = 100;
      int $i$f$calculate3 = false;
      int var7 = false;
      int value2 = x$iv + x$iv;
}

可以看到函数自身传入的lmabda表达式均被内联到调用处。

内联可能导致生成的代码增加。不过如果使用得当(避免内联过大函数),性能上会有所提升,尤其是在循环中的“超多态”调用处

如果不希望内联所有传给内联函数的lambda表达式参数,可以用noliline 修饰符标记不希望内联的函数参数

inline fun calculate3(x: Int, noinline cal: (Int) -> Int): Int {
    return cal(x)
}

fun main() {
    val value2 = calculate3(100) { x -> x + x }
}

反编译之后:

public final void main() {
      byte x$iv = 100;
      Function1 cal$iv = (Function1)null.INSTANCE;
      int $i$f$calculate3 = false;
      int value2 = ((Number)cal$iv.invoke(Integer.valueOf(x$iv))).intValue();
}

具体化类型参数

内联函数 (普通函数不能具体化参数)中使用reified修饰符来限定类型参数使其可以在函数内部访问它,几乎就像是一个普通的类一样。示例:

inline fun <reified T> isInstance(o: Any): T? {
    if (o is T) {
        return o
    }
    return null
}

fun main() {
    val test: Any = "1234"
    val test2: String? = isInstance<String>(test)
}
//可以用于在循环中转换参数类型

内联属性

幕后字段:当一个属性需要幕后字段时,Kotlin会自动提供。这个幕后字段可以通过field标识符在访问器中引用。

var counter = 0 // 注意:这个初始器直接为幕后字段赋值
    set(value) {
        if (value >= 0) field = value
    }

如果属性至少一个访问器使用默认实现,或者自定义访问器通过 field 引用幕后字段,将会为该属性生成一个幕后字段。

例如,下面的情况下, 就没有幕后字段:

val isEmpty: Boolean
    get() = this.size == 0

inline修饰符可用于没有幕后字段的属性的访问器

val foo: Int
    inline get() = 1

var foo2: Int
    get() = 1
    inline set(value) {

    }

inline var foo3: Int
       get() = 1
       inline set(value) {

      }

在调用处,内联访问器如同内联函数一样内联。

公有API内联函数的限制

当一个内联函数是public或者protected而不是privateinternal声明的一部分时,就会认为它是一个模块级的公有API。

可以在其他模块中调用它,并且也可以在调用处内联这样的调用。 这带来了一些由模块做这样变更时导致的二进制兼容的风险—— 声明一个内联函数但调用它的模块在它修改后并没有重新编译。为了消除这种由非公有 API 变更引入的不兼容的风险,公有 API 内联函数体内不允许使用非公有声明,即,不允许使用 privateinternal 声明以及其部件。

一个 internal 声明可以由 @PublishedApi 标注,这会允许它在公有 API 内联函数中使用。 当一个 internal 内联函数标记有 @PublishedApi 时,也会像公有函数一样检测其函数体。

val double: (Int) -> Int = { x -> x * x }
private fun calculate2(x: Int, cal: Int.() -> Int): Int {
        return cal(x)
}

@PublishedApi
internal fun calculate(x: Int, cal: (Int) -> Int): Int {
        return cal(x)
}

inline fun calculate3(x: Int, noinline cal: (Int) -> Int): Int {
        //calculate2(x, double) //报错
        calculate(x, double)
        return cal(x)
}

七、操作符重载

在Kotlin中可以为类型提供预定义的一组操作符的自定义实现。这些操作符具有预定义的符合表示(如+或者*)与优先级。

操作符和想要的指定函数关系:操作符重载关系表

使用operator修饰符修饰函数可以对相应的操作符进行操作

示例:++操作符对应的函数是inc()

class Couple(val i: Int = 0) {

    operator fun inc(): Couple {
        return Couple(i + 2)
    }
}

fun main() {
    var couple = Couple(0)
    couple++
    Log.e("KotlinTest", "couple.i:${couple.i}") //输出i=2
}

其他操作符重载类似