Kotlin语法基础篇三:函数

前言:

        前两篇文章介绍了Kotlin中的基本数据类型属性和控制流。本篇文章我们将介绍Kotlin中比较重要的一个知识点:函数。对于函数,我想每个开发同学都不会陌生。善于将复杂的逻辑拆分成多个功能函数,将对我们代码的可读性和可维护性会有很大的帮助。而将多个复杂的逻辑放在同一个方法中,可能会让代码变得十分臃肿。下面让我们开始本篇文章的学习。

1.函数的声明

在Kotlin中我们用关键字fun来声明一个函数。一个简单的函数声明如下: 

fun sum(left: Int, right: Int): Int {
        return left + right
}

 关键字fun后面紧跟着函数名,通常函数名都是以这个函数实现的功能来命名的。函数名后紧跟着小括号(),定义该函数所需要的参数。即name:type。

多个参数用逗号隔开(name:type, name:type)。如果不需要参数,直接用()即可。如果该函数需要返回值则在()后面添加 :type。花括号{ }则代表这个函数的方法体或者说是这个函数的作用域。

2.函数的默认参数

通常在Java中函数我们称之为方法,相比较Java语言,Kotlin中的函数允许有参数默认值。而允许方法的参数可以拥有默认值,可以减少方法的重载(方法名相同,方法的参数类型和个数不同,我们称之为方法的重载)。如下函数的定义:

fun sum(left: Int, right: Int): Int {
        return left + right
}

// right参数具有默认值0
fun sum(left: Int, center: Int, right: Int = 0): Int {
        return left +center + right
}

当我们给sum函数的参数right添加了默认值以后。在函数调用的地方,我们可以选择性的传或者不传这个参数。

fun main() {
    sum(0, 10)
    sum(0, 10, 15)
}

fun sum(left: Int, center: Int, right: Int = 0): Int {
    return left +center + right
}

善于使用函数参数的默认值,在实际开发过程中,可以减少像Java语言中的方法重载。

3.具名参数 

当我们调用一个拥有众多参数的函数时,参数类型和参数名匹配起来比较麻烦时。具名参数的使用就变得很有意义。使用具名参数可以不用考虑参数在函数中声明的顺序,使用propertyName = propertyValue的方式传入参数。假设我们有如下拥有多个参数的函数fold:

fun main() {
    fold(right = false, center = "center", left = 0, isEmpty = false)
}

fun fold(left: Int, center: String, right: Boolean, isEmpty: Boolean) {
    // ...省略逻辑
}

fold函数拥有4个参数,当我们在main函数中使用具名参数的方式调用fold函数,我们无需再考虑函数参数在声明时的位置,只要将所有的参数具名传入即可。当然在实际开发的过程中,一个复杂函数的参数远不与此。

4.函数的默认返回值Unit

当一个函数没有返回值时,它将拥有一个默认的返回值类型Unit。通常情况我们都会省略它。如下示例,我们将fold函数的返回值显示的声明成Unit。

fun fold(initValue: Int):Unit {
    // ...省略逻辑
    return Unit
}

// 省略Unit
fun fold(initValue: Int) {
    // ...省略逻辑
    return 
}

 返回时,我们可以直接使用return,也可以显示的加上Unit返回值类型。

5.单函数表达式

在上一篇文章属性和控制流中我们介绍到,当if或者when表达式的分支块中仅有一行表达式时,我们可以省略分支块的花括号。而Kotlin中的函数亦是如此。当函数返回单个表达式时,可以省略花括号并且在 = 符号之后指定代码体即可:

fun sum(left: Int, right: Int): Int = left + right 

// Kotlin编译器可以推断出函数的返回值类型,可省略函数的返回值类型
fun sum(left: Int, right: Int) = left + right 

 既然if或者when也可以作为单个表达式,那么我们也可以将一个函数用=连接一个if或者when的表达式。

fun isEmpty(str:String) = if(str.length == 0) true else false

// 示例代码
fun findNum(num:Int) = when(num) {
    1001 -> 0
    1002 -> 1
    else -> -1
}

6.可变数量的参数

在Java中我们使用propertyType... propertyName的方式来声明一个可变数量的参数:

private void sum(Integer... value) {
        // ...
}

而在Kotlin中我们使用关键字vararg来声明可变数量的参数。一个方法只能有一个参数是用vararg修饰的。当我们调用 vararg-函数时,我们可以一个接一个地传参。如下示例代码:

fun main() {
    sum(1, 2, 3)
}

fun sum(vararg value:Int) {
    for(i in value) {
        println("i = $i")
    }
}

// 输出
i = 1
i = 2
i = 3

当一个vararg-函数有多个参数时,如果vararg参数不是最后一个参数,其后的参数可以使用具名参数的方式传递:

fun main() {
    sum(1, 2, 3, b = 10, c = 100)
}

private fun sum(vararg value: Int, b: Int, c: Int) { }

如果我们已经有一个数组并希望将其内容传给该函数,我们使用**伸展(spread)** 操作符(在数组前面加 *):

fun main() {
    val array = intArrayOf(1, 2, 3)
    sum(*array, b = 3, c = 4)
}

private fun sum(vararg value: Int, b: Int, c: Int) { }

 7.中缀表示法 

在Kotlin中使用infix关键字修饰的函数可以使用中缀表示法,忽略该函数调用时的点和括号。该函数必须满足如下条件:

  • 用infix修饰的函数有且只能有一个参数
  • 必须是成员函数或者扩展函数(关于扩展函数后面会详细讲解)
  • 不能接受vararg声明的可变数量的参数

典型的应用,我们可以看Kotlin中给我们提供的Tuples.kt中的数据类Pair:

public data class Pair<out A, out B>(
    public val first: A,
    public val second: B
) : Serializable 
// ...

public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

// ...

由上面的代码中我们可以看到数据类Pair拥有infix修饰的扩展函数to -> A.to(that: B)。下面我们来看一下在实际开发中的使用:

fun main() {
    val pair = 20 to "age"
    println("pair = $pair")
}

// 输出
pair = (20, age)

中缀表示法可以让我们的函数调用像阅读英文一样的优雅。使用A to B的方式创建了一个Pair对象,省略了调用函数时的点和括号。下面我们使用infix关键字来实现一个我们自己定义的函数:

infix fun <T> Collection<T>.has(item: T) = if (item in this) true else false

fun main() {
    val languageList = arrayListOf("kotlin", "ios", "android")
    if (languageList has "test") {
        println("There is this language")
    } else {
        println("There is no such language")
    }
}

// 输出
There is no such language

8.函数作用域

1.在Kotlin中函数可以在文件顶层声明。不需要像Java那样要创建一个类来保存一个函数。对与在文件顶层声明的函数默认情况下,我们可以在当前文件所在的项目中任意地方访问,除非你将函数显示的加上private访问修饰符。选中当前项目,右击鼠标或者触摸板。New -> Kotlin Class/File,在弹出的选择框中,我们选择File,声明一个Fun.kt的文件。

// 当前项目中可以调用
fun sum(left: Int, right: Int): Int {
    return left + right
}

// 当前文件中可以使用
private fun fold() {
    println("fold called")
}

2.成员函数。

在类内部或者对象内部创建的函数我们称之为成员函数,以点表示法调用。 

class Student {
    fun study() {
        println("I enjoy learning")
    }
}

fun main() {
    Student().study()
}

3.局部函数。(在Kotlin中支持一个函数嵌套另外一个函数)

在函数内部声明一个函数,我们就称这个函数为局部函数。如下示例,我们在main函数的内部声明了一个getMax函数。

fun main() {
    val a = 1001
    val b = 1002

    // 局部函数
    fun getMax() :Int {
        return if(a > b) {
            println("a = $a")
            a
        } else {
            println("b = $b")
            b
        }
    }

    println("max = ${getMax()}")
}

// 输出
b = 1002
max = 1002

局部函数可以访问外部函数声明的属性。 

9.泛型函数 

在Kotlin中我们通常在关键字fun之后紧跟<T>来声明一个泛型函数。(大写字母放在<>中,T是习惯性写法,也可以用别的大写字母表示),如下:

fun <T> getData(data:T) { 
    println("data = $data")
}

我们也可以将声明的泛型类型作为函数的返回值:

fun <T> getData(data:T) :T {
    println("data = $data")
    return data
}

当声明的方法拥有多个泛型时,在<>括号中用逗号将泛型隔开:

fun <T, R> getData()  { }

关于Kotlin中泛型的使用我们会在后续的文章中详细介绍。 

 10.扩展函数和扩展属性

在Kotlin中我们可以在不通过继承类的方式,来给一个已知类扩展一些额外的方法。具体语法结构如下:

fun ClassName.funName(name:type) :type {
    return name
}

对于扩展函数,除了在函数名前加上一个ClassName.,其它的语法和定义一个普通的函数都是一样的。在方法名前加上ClassName.就表示我们要将该函数定义在那个类中。

通常我们都会将扩展函数定义成顶层函数,这样我们就可以拥有全局的访问域。在AndroidStudio中选中当前项目,右击鼠标或者触摸板,New -> Class/File在弹出的选择框中我们选择File,然后创建一个名为Extend.kt的文件。现在我们来给Array类添加一个交换数组元素的扩展方法,并在main函数中访问它,如下:

fun Array<Int>.swap(leftIndex: Int, rightIndex: Int) {
    val temp = this[leftIndex]
    this[leftIndex] = this[rightIndex]
    this[rightIndex] = temp
}

fun main() {
    val array = arrayOf(0, 1, 2)
    array.swap(0, 2)
    for (index in array.indices) {
        println("index = $index, value = ${array[index]}")
    }
}

// 输出
index = 0, value = 2
index = 1, value = 1
index = 2, value = 0

从打印的结果来看,我们使用扩展函数swap成功的将数组array中的两个元素完成了交换。在扩展函数内部,我们可以使用this关键字来表示当前接受者(传过来的在点符号前的对象)。

当然我们也可以将swap函数写成泛型函数的形式:

fun <T> Array<T>.swap(leftIndex: Int, rightIndex: Int) {
    val temp = this[leftIndex]
    this[leftIndex] = this[rightIndex]
    this[rightIndex] = temp
}

而实际开发中这种需求是我们能经常遇到的, 结合下一章我们要介绍的高阶函数,可以给我的开发带来很大的便捷。

关于扩展属性,其实和普通类的属性对比。也只是在属性名前加上了ClassName.。表示我们需要将这个属性定义在那个类中。例如我们上面在遍历array数组的时候,就用到Array类的扩展属性indices,它的具体实现如下代码:

public val <T> Array<out T>.indices: IntRange
    get() = IntRange(0, lastIndex)

关于扩展属性这里有一个点需要注意:由于扩展没有实际的将成员插入类中,因此对扩展属性来说幕后字段是无效的。这就是为什么扩展属性不能有初始化器。他们的行为只能由显式提供的 getters/setters 定义。 

fun String.length = 0 // 错误:扩展属性不能有初始化器

 总结

        到这里关于Kotlin中函数的介绍我们就写完了,下篇文章我们将详细介绍Kotlin中的高阶函数和Lambda表达式,我们下期再见。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值