前言:生命不息,奋斗不止,只要相信,只要坚持。
对于 Kotlin 中的函数来说,和 JavaScript 语言很像,对于 Java 来说它不是闭包的,这里主要讲函数的基本使用,包括函数的定义,参数,返回值等。同时介绍函数参数使用默认值和单表达式函数的使用。
Kotlin中函数的声明关键字为fun
,定义的各式为:
可见修饰符 fun 函数名(参数名 : 参数类型) : 返回值类型{}
注意:默认为public
可见修饰符,小括号()
是必须存在的,即使没有参数的情况,大括号{}
也是必须存在,即使没有函数体。如果函数没有返回值则可以省略返回值类型。例如:
//参数numA,numB是Int类型,返回值是Int类型
fun sum(numA: Int, numB: Int): Int {
return numA + numB
}
成员函数是指在类或者对象的内部函数。声明一个类,在类中定义一个方法,这个方法就是这个类的成员函数。
class FunctionActivity {
fun basic(){}
}
成员函数的使用和 Java 类似,调用成员函数使用点表示法:
//成员函数直接调用
basic()
var str = basic()//有返回值
创建类FunctionActivity的实例并调用basic()
FunctionActivity().basic()
var str2 = FunctionActivity().basic()//有返回值
有关类和重写成员的更多信息,请参考类和继承。
定义一个有参数的函数,使用Pascal
法表示定义,即:name : type
。其中参数是必须有显示的参数类型,并且参数与参数之间用,
隔开。
//参数numA,numB是Int类型,返回值是Int类型
fun sum(numA: Int, numB: Int): Int {
return numA + numB
}
Log.e(TAG, "带参数的函数:sum == ${sum(1, 2)}")
打印数据如下:
带参数的函数:sum == 3
对于默认参数,即使函数中的参数具有默认值,这样可以使用该函数的时候,这减少了过载的数量,减少函数重载,灵活性更高。格式为:name : type = 默认值
//默认参数
fun defaultArgs(argsA: Int? = 0, argsB: Float = 1f, argsC: Boolean = false) {
Log.e(TAG, "默认参数:argsA == $argsA | argsB == $argsB | argsC == $argsC")
}
//默认参数使用
defaultArgs()
defaultArgs(null, 20f)
打印数据如下:
默认参数:argsA == 0 | argsB == 1.0 | argsC == false
默认参数:argsA == null | argsB == 20.0 | argsC == false
如果函数中参数有默认值,可以对有默认值的参数不传递参数值,会使用默认值。
注意:当该函数是一个成员函数,并且该函数复写继承类中的方法,则该函数必须从签名中省略该函数的默认值,在你复写方法时,编辑器默认帮你实现。
open class A {
open fun defaultArgsOver(i : Int = 10){}
}
class B : A() {
//复写A中的方法
override fun defaultArgsOver(a: Int) {
super.defaultArgsOver(a)
Log.e(TAG, "默认参数:argsOver == $a")
}
}
//调用
B().defaultArgsOver(1)
打印数据如下:
默认参数:argsOver == 10
如果在默认参数之后的最后一个参数是一个lambda,你可以把它作为一个命名参数放在括号外面:
fun play(weight: Int = 0, height: Int = 1, qux: () -> Unit) {
//TODO
}
fun main(args: Array<String>) {
play(1) { println("play") }
play(qux = { println("play") })
play { println("play") }
}
命名参数是指使用函数传递参数时,可以命名参数传入的格式是 参数名 = 参数值
这种形式的一个或多个参数。在函数有个多个参数(有默认值)时,如果只需要传一部分参数,而其他参数使用默认值时,那么我们只需要指定命名参数就可以了。
当你在函数调用中使用命名参数时,你可以自由地更改列出它们的顺序,如果你想使用它们的默认值,你可以完全不适用命名参数。
当调用这个函数时,你不需要命名它的所有参数:
//命名参数
fun nameArgs(str: String, num: Int = 0, float: Float = 1.0f, isTrue: Boolean = false) {
Log.e(TAG, "命名参数:str == $str | num == $num | float == $float | isTrue == $isTrue")
}
//使用 `参数名 = 参数值` 的形式
nameArgs("Kotlin", 1000, argsC = 20f, argsD = true)
nameArgs("Andorid")//跳过所有参数赋值,将使用默认值
打印数据如下:
命名参数:argsA == Kotlin | argsB == 1000 | argsC == 20.0 | argsD == true
命名参数:argsA == Android | argsB == 0 | argsC == 1.0 | argsD == false
在JVM上,在调用 java 函数时不能使用命名参数语法,因为 java 字节码并不总是保留函数参数的名称。
函数中的可变长参数使用vararg
修饰,即当函数中的参数是不定个数并且是同一类型,则可以使用vararg
修饰这个参数,被它修饰的参数相当于一个固定类型的数组。格式如下:
fun 函数名 (vararg 参数名 : 参数类型) : 返回值 {}
举个栗子:
//可变参数
fun varargArgs(vararg strs: String) {
for (item in strs) {
Log.e(TAG, "可变参数:strs == $item")
}
}
//调用
varargArgs("1", "2", "3", "4")
打印数据如下:
可变参数:strs == 1 2 3 4
在函数内部,类型为 T 的可变参数可以看作为 T 的数组,也就是说,下面示例中的 arguments 变量的类型为 Array
。
//vararg 修饰表示为可变参数
fun <T> asList(vararg arguments: T): List<T> {
val result = ArrayList<T>()
for (arg in arguments) {
result.add(arg)//arguments 是一个数组,类型为 T 的可变参数可以看作为 T 的数组
}
return result
}
fun main(args: Array<String>) {
val list = asList(1, 2, 3, 4)
}
只有一个参数可以标记为可变参数。如果可变参数不是列表中的最后一个参数,则可以使用命名参数语法传递一下参数的值,如果参数具有函数类型,则通过传递一个 lambda 外括号。
当我们调用一个可变参数函数时,我们可以一个一个传递参数,例如asList(1, 2, 3, 4)
,或者如果我们已经有一个数组,想要传递它的内容给函数,则使用扩展操作符(在数组前面加上 *
前缀):
val nums = arrayOf(8, 9, 10)
val lists = asList(-1, 0, *nums, 1, 21)
println("lists == $lists")
那么 nums 就能将它的内容传递给函数,打印数据如下:
lists == [-1, 0, 8, 9, 10, 1, 21]
在 Kotlin 中,函数的返回值可以分为两种:
Unit
类型: 表示无返回值的情况,可以省略;Unit
类型的返回值函数的返回值是Unit
类型的时候表示不需要返回值,可以省略,此类型对应于 Java 中的void
类型。
//无返回值, Unit修饰
fun unitReturn(): Unit {
//return Unit 或者 return 是可选
}
//无返回值
fun unitReturn() {
}
上面两个函数其实是等价的。
其他类型的返回值说明该类型有返回值,并且返回值类型不能省略,返回值也不能省略。
//返回值为String类型
fun otherReturn(): String {
return ""
}
//返回值为Int类型
fun otherReturn2(): Int {
return 0
}
明确返回类型:具有 body 的函数必须始终显式指定返回类型,除非它打算让它们返回 Unit
,在这种情况下,它是可选的,Kotlin 不会自动推断具有 body 的函数的返回类型,因为这样的函数在 body 中可能有复杂的控制流,并且返回类型对读者(有时甚至对编译器)并不明显。
单表达式函数表示在函数具备返回一个表达式时,可以省略花括号{}
直接用=
赋值指定的body ,而函数的返回值是有编译器自动推算的。
//1.完整形式
fun double(num: Int): Int {
return 2 * num
}
//2.简化后,省略大括号
fun double(num: Int): Int = num * 2
//3.省略显示返回类型
fun double(num: Int) = num * 2
显式声明返回类型是可选的,可以由编译器来推断。
用 infix
关键字标记的函数也可以使用 infix
表示法调用(省略调用的点和括号)。infix
函数必须满足以下要求:
infix
函数调用的优先级低于算术运算符、类型强制转换和rangeTo运算符。以下表达式是等价的:
1 shl 2 + 3 等价于 1 shl (2 + 3)
0 until n * 2 等价于 0 until (n * 2)
xs union ys as Set<*> 等价于 xs union (ys as Set<*>)
另一方面,infix
函数调用的优先级要高于布尔运行符 &&
和 ||
、 is-
和 -check
以及其他一些运算符。这些表达也是等价的:
a && b xor c 等价于 a && (b xor c)
c xor b in c 等价于 (a xor b) in c
注意:infix
函数始终需要制定接收方和参数。当你使用 infix
符号在当前接收器上调用一个方法时,你需要显式地使用 this
,与常规的方法调用不同,它不能被忽略。这是确保明确解析所必需的。
class FunctionsActivity {
infix fun add(str: String) {
//TODO
}
fun addCurrentStr() {
this add "Android" //正确
add("Kotlin") //正确
//add "Java" //错误, 必须指定接收方
}
}
Kotlin 支持一种称为尾部递归的函数式编程风格。这允许使用递归函数编写一些通常使用循环编写的算法,但没有堆栈溢出的风险。当一个函数被标记为 tailrec
修饰符并且满足要求的形式时,编译器会优化递归,留下一个快速高效的基于循环的版本替代:
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))
这段代码计算余弦的不动点,它是一个数学常数,它只叫数学。cos 从1.0开始重复,直到结果不在变化,对于指定的 eps
精度,结果为0.7390851332151611。产生代码相当于这个更传统的风格:
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
修饰符的条件,函数必须在最后执行的操作中调用自己。当递归调用后还有更多代码时,不能使用尾部递归,而且不能在 try/catch/finally
块中使用尾部递归。目前 Kotlin 对 JVM 和 Kotlin/Native 都支持尾递归。
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())
}
局部函数可以访问外部函数的局部变量(即闭包),因此在上面的例子中,访问的可以是一个局部变量:
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])
}
在 Kotlin 中,函数可以在文件的顶层声明,这意味着你不需要创建一个类来保存函数,而在Java,c#或Scala等语言中需要这样做。除了顶级函数,Kotlin 函数也可以声明为本地函数,作为成员函数和扩展函数。
函数可以有泛型参数,在函数名前调用尖括号 <>
指定:
fun <T> invite(item: T): List<T> {
//TODO
}
有关泛型函数的更多信息,请参考泛型函数。
有关内联函数的更多信息,请参考内联函数。
有关扩展函数的更多信息,请参考扩展函数。
高阶函数和Lambdas也在各自的文章中讲解。请参考高阶函数和lambdas
源码地址:https://github.com/FollowExcellence/KotlinDemo-master
好了各位,以上就是这篇文章的全部内容了,能看到这里的人呀,都是人才。
我是suming,感谢各位的支持和认可,您的点赞、评论、收藏【一键三连】就是我创作的最大动力,我们下篇文章见!
如果本篇博客有任何错误,请批评指教,不胜感激 !
要想成为一个优秀的安卓开发者,这里有必须要掌握的知识架构,一步一步朝着自己的梦想前进!Keep Moving!