Kotlin学习笔记——函数

函数的声明

函数的声明使用关键字fun,格式为 [访问限定符] fun 函数名([参数名: 参数类型])[: 返回值类型] {}

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

函数的调用

  • 普通调用
add(1, 2)
  • 通过类调用函数
    通过类的实例调用函数,使用.连接符调用
Math().add(1, 2) // 调用Math类里面的add函数

函数的参数

函数参数使用 Pascal 表示法定义,即 name: type。参数用逗号隔开。每个参数必须有显式类型:

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

参数默认值

函数参数可以有默认值,函数声明是如果制定了默认值,在调用函数是可以省略相应的参数。与其他语言相比,这可以减少重载数量。函数参数设置默认值的格式是name: type = default_value

fun add(x: Int = 0, y: Int = 0): Int {
    return x + y;
}

// 设置了默认值,函数的调用以下三种都是合法的
println(add())

println(add(1))

println(add(1, 2))
  • 覆盖方法时自动使用与基类型方法相同的默认参数值。 当覆盖一个带有默认参数值的方法时,签名中必须省略默认参数值:
open class Math {
    open fun add(x: Int, y: Int = 0): Int {
        return x + y
    }
}

class Math2 : Math() {
    // 函数的签名中不能有默认值设置
    override fun add(x: Int, y: Int): Int {
        return super.add(x, y)
    }
}

Math2().add(1) // 默认使用基类中被重写函数的默认值设置
  • 如果函数有默认值参数后面,有无默认值的参数,那么调用时不能省略设置了默认值的参数;
fun add(x: Int = 0, y: Int): Int {
    return x + y
}

println(add(1)) // 错误,编译器江认为传入的参数是第一个参数

println(add(1, 2)) // 正确,第一个参数虽然设置了默认值,但是后面还有无默认值的参数,所以第一个参数不能省略
  • 如果在默认参数之后的最后一个参数是 lambda 表达式,那么它既可以作为命名参数在括号内传入,也可以在括号外传入:


fun add(x: Int, y: Int = 0, qux: () ->Unit): Int {
    return x + y
}

// lambda表达式可以放在括号里面,也可以放在括号外面
add(1, 2) { println("Add x and y") }
// 默认值后面跟着lambda表达式参数,默认值参数依旧可以省略
add(1, qux = { println("Add x and y") })

命名参数

命名参数,就是在调用函数时使用param_name = param_value的方式制定参数值,这个在默认参数中的作用非常大,可以解决上面提到的“如果默认参数后面存在无默认值的参数情况下不能省略默认参数”的问题,具体可以看以下的例子:

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

Math(y = 2) // 命名参数,指定参数值是y,那么就可以省略掉本身有默认值的x

命名参数非常有用,假设有一个方法存在许多个默认值参数,然而在调用时,我只需要改变某一个默认值时,使用命名参数,就无需将所有无需改变默认值的参数一一列出来。

fun add(x: Int, y: Int = 0, z: Int = 0, a: Int = 0, b: Int = 0): Int {
    return x + y + z + a + b
}

// 使用命名参数,调用函数时只需要对需要设置值的默认值参数进设置值即可
add(6, z = 2)
add(6, a = 2)
  • 相对于命名参数,直接设置参数值的叫做“位置参数”,在调用函数是,所有的位置参数都必须在命名参数之前;
  • 如果Kotlin调用java,不能使用命名参数,因为java不支持命名参数。

返回Unit的函数

如果一个函数不返回任何有用的值,它的返回类型是 Unit。这个跟Java中的 void 类似,但是不同的是,Kotlin中这个值不需要显式返回(默认就是返回 Unit ),而Java的 void 不能省略。

// 不返回任何值,返回值类型是Unit
fun printMsg(msg: String): Unit {
    println(msg)
}

// Unit可以省略,不需要显式返回
fun sendMsgMsg(msg: String) {
    println(msg)
}

单表达式函数

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

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

显式返回值类型

除了返回值类型是 Unit 的可以忽略外,其他类型都必须显示指定返回值类型。Kotlin 不推断具有块代码体的函数的返回类型,因为这样的函数在代码体中可能有复杂的控制流,并且返回类型对于读者(有时甚至对于编译器)是不明显的。

fun add(x: Int, y: Int): Int { // 函数体是代码块,必须显示声明返回值类型
    val z = x + y
    return z > 0 ? z : 0
}

可变数量的参数

跟Java一样,函数的参数可以定义成可变数量的,在Kotlin中可变数量参数使用 vararg 关键字定义,可变参数实际就是指定类型的一个数组,可以使用访问数组的方式访问每个变量。

  • 如果可变参数是函数参数中的最后一个,调用是可直接传入无数个用逗号隔开的值即可。
fun add(vararg args: Int): Int {
    // args实际就是Array<Int>
    println("Args size: " + args.size)
    var sum = 0
    for (arg in args) { 
        sum += arg
    }
    return sum
}

add(1, 3, 5, 6, 2, 9, 22, 33)
  • 如果函数的参数中存在其他参数(非可变),而且可变参数不是在参数声明的最后, 可以使用命名参数语法传递其后的参数的值,或者,如果参数具有函数类型,则通过在括号外部传一个 lambda。
fun add(vararg args: Int, tag: String): Int {
    println("Args size: " + args.size)
    var sum = 0
    for (arg in args) {
        sum += arg
    }
    return sum
}

add(1, 2, 3, 4, 5, 6, 7, tag = "XXXX") // 可变参数不是最后一个,可用命名参数实现传值

中缀表示法

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

  • 它们必须是成员函数或扩展函数;
  • 它们必须只有一个参数;
  • 其参数不得接受可变数量的参数且不能有默认值。
class Math {
    fun add(x: Int = 0, y: Int): Int {
        return x + y
    }

    infix fun infixFun(x: Int) {
        println("Param value is $x")
    }
}

// 这两种写法等同
Math() infixFun 222
Math().infixFun(222)

  • 需要注意的是,中缀函数总是要求指定接收者与参数。当使用中缀表示法在当前接收者上调用方法时,需要显式使用 this;不能像常规方法调用那样省略。这是确保非模糊解析所必需的。
class Math {
    fun add(x: Int = 0, y: Int): Int {
        this infixFun (x + y) // 必须制定接收者与参数,接收者为当前对象是,必须显示调用this关键字
        return x + y
    }

    infix fun infixFun(x: Int) {
        println("Param value is $x")
    }
}

函数作用域

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

局部函数

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

fun sum(vararg args: Int): Int {
    var sum = 0
    // 在Kotlin中,支持在一个函数内部定义一个函数,作用域只在这个函数的内部
    fun add(x: Int, y: Int): Int {
        println("Before calculate the sum is $sum") // 在局部函数内部,可以访问外部函数中的变量
        return x + y
    }
    
    for ( arg in args) {
        sum = add(sum, arg)
    }
    
    return sum
}

成员函数

成员函数,就是在类内部定义的函数,叫做这个类的成员函数。

class Math {

    fun sum(vararg args: Int): Int {

        var sum = 0
        fun add(x: Int, y: Int): Int {
            println("Before calculate the sum is $sum")
            return x + y
        }
        for ( arg in args) {
            sum = add(sum, arg)
        }

        return sum
    }



    infix fun infixFun(x: Int) {
        println("Param value is $x")
    }
}

泛型函数

在Kotlin中,可以在定义函数的时候定义泛型(在Java中是无法做到的,泛型的定义只能在类声明中)。

fun <T> log(vararg args: T) {
    for(arg in args) {
        println(arg.toString())
    }
}

高阶函数

高阶函数是将函数用作参数或返回值的函数。

函数类型

函数类型,是具有与函数签名(即它们的参数和返回值)相对应的特殊表示法。

  • 所有函数类型都有一个圆括号括起来的参数类型列表以及一个返回类型:(A, B) -> C 表示接受类型分别为 AB 两个参数并返回一个 C 类型值的函数类型。 参数类型列表可以为空,如 () -> AUnit 返回类型不可省略。

  • 函数类型可以有一个额外的接收者类型,它在表示法中的点之前指定: 类型 A.(B) -> C 表示可以在 A 的接收者对象上以一个 B 类型参数来调用并返回一个 C 类型值的函数。 带有接收者的函数字面值通常与这些类型一起使用。

  • 挂起函数属于特殊种类的函数类型,它的表示法中有一个 suspend 修饰符 ,例如 suspend () -> Unit 或者 suspend A.(B) -> C

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

(Int, Int) -> Unit // 返回值是Unit不能省略

() -> Int // 参数列表可以为空

Math.(Int, Int) -> Int // 函数类型可以指定一个接收者
  • 如需将函数类型指定为可空,请使用圆括号将整个类型括起来:((Int, Int) -> Int)?
  • 函数类型可以使用圆括号进行接合,这样可以表示多级的函数类型:(Int) -> ((Int) -> Unit)
  • 箭头表示法是右结合的(即从右),(Int) -> (Int) -> Unit 与前述示例等价,但不等于 ((Int) -> (Int)) -> Unit
  • 函数类型可以起一个类型别名。

函数类型实例化

函数类型的实例化可以通过集中方法

  • lambda表达式: val f: Math.(Int, Int) -> Int = {x, y -> x + y}
  • 匿名函数:val f: Math.(Int, Int) -> Int = fun (x: Int, y: Int): Int {return x + y}
  • 使用已有声明的可调用引用(顶层、局部、成员、扩展函数或者属性):val f: (Int, Int) -> Int = ::add

函数类型实例的调用

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

println(f.invoke(Math(), 1, 2)) // 如果函数类型有接收者,那么他的第一个参数必须传入接收者对象

println(f2.invoke(3, 4))

println(f(Math(), 1, 2)) // 如果函数类型有接收者,那么他的第一个参数必须传入接收者对象

println(f2(3, 4))
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章