前言:
前两篇文章介绍了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表达式,我们下期再见。