函数
定义
-
使用 fun 关键字定义一个函数。fun 后为函数名,函数名后为参数列表,参数列表后为冒号,冒号后为返回值类型。
函数格式 -
如果函数返回值为 void,则可以省略冒号及返回值类型。
fun test(a: Int) { // 该函数没有返回值
println("a = $a")
}
fun test2(b: Int): String { // 该函数返回值类型为 String
return "$b"
}
参数
-
不能使用 var 或 val 修饰
-
必须指明类型。如:
fun test(a:Int,b:Int = 3 ) = a+b
fun main(args:Array<String>){
println(test(2)) //5
print(test(3,4)) // 7
}
命名参数
在调用函数时,可以使用参数名指定为某个参数传值。
命名参数主要是为了提高代码的可读性 —— 阅读代码时,可以很方便的知道不同的实参是为哪个形参进行赋值。
-
参数的顺序可以调换。
fun main(args: Array<String>) { println(test(a = 1, c = 2, b = "bb")) } fun test(a: Int, b: String, c: Int) = "a = ${a},b = ${b},c = ${c}"
-
命名参数的后面所有参数必须是命名参数。如上述代码中,如果改写成如下格式便会出错,因为 b 使用了命令参数,而后面没有使用:
println(test(1, b = "bb", 2))
-
命名参数只能在 kt 之间使用,kt 调 java 不支持。
默认参数值
kt 中,在声明函数时可以为参数指定默认值。当调用者没有传递该参数时,该参数的值为默认值。
默认参数的主要原因是为了避免太多的重载方法。
-
当按常规方法调用函数时,除了最后设置有默认值的参数可以省略,必须为每一个参数进行赋值。可以省略末尾的有默认值的参数,中间的不参省略。
如下示例中,传参时必须为 b 进行赋值,但可以省略 d 的实参。常规调用时,没办法做到只为 a,c 赋值。
fun main(args: Array<String>) { println(test(1, "bb",2)) } fun test(a: Int, b: String = "bb", c: Int,d:Int=3) = "a = ${a},b = ${b},c = ${c},d=${d}"
-
命名参数时,可以按任意顺序,省略所有有默认值的参数。如下,在调用时,完全没有考虑顺序,也没有为任何一个有默认值的参数进行传参。
fun main(args: Array<String>) { println(test(c = 2,a = 1)) } fun test(a: Int, b: String = "bb", c: Int,d:Int=3) = "a = ${a},b = ${b},c = ${c},d=${d}"
表达式函数体
当一个函数的函数体中只有一个表达式的时候,可以变成表达式函数。
所谓的表达式函数指的是:除掉普通函数的大括号以及 return 语句,并使用等号将表达式连接在参数列表后。
同时,也可以省略返回值类型。
fun test(b: Int) = "$b"
fun test2(b:String):String = "$b is b"
在第一个表达式函数中,并没有指定返回值类型。因为函数体只是一个表达式,而表达式的返回值可以经过类型推导得到。
顶层函数
kt 中函数可以不依赖于任何类而单独存在。这些函数被称为顶层函数。
因为 jvm 只能调用类中的方法,因此 kt 被编译后,顶层函数会被编译到一个与文件名相同的类中,并且成为该类中的一个静态函数。
例如 kotlin 中代码如下:
// 文件名为 Demo.kt
package com.demo
fun test(a:Int,b:String) = "${a}+${b}"
经过编译后,会生成 DemoKt 类,而 test 是该类中的一个静态方法。其调用过程如下:
import com.demo.DemoKt; // 导入 kt 文件编译后生成的类
public class Test {
public static void main(String[] args){
String s = DemoKt.test(1, "2"); // 调用类中的静态方法
System.out.println(s);
}
}
@file:JvmName
指定 kt 文件编译后的类名。
该注解必须使用在文件的第一行,凌驾于 package 之上。如将上述文件修改如下,那么调用时就应该使用 Utils.test ,而不是 DemoKt.test。
@file:JvmName("Utils")
package com.demo
fun test(a:Int,b:String) = "${a}+${b}"
顶层属性
与顶层函数一样,属性也可以不依赖任何类而单独存在。该定义的属性,与普通的类中的属性一样,java 使用时需要通过 getter / setter 进行处理。
@file:JvmName("Utils")
package com.demo
var count = 0
val age = 10
Java 使用时,如下:
Utils.getAge();
Utils.setCount(1);
Utils.getCount();
const
定义常量的顶层属性。
上述定义的顶层属性,都会生成 getter / setter 方法以供 java 调用,并不是 java 中的 public static final 。如果使用 const 修饰的变量转换成 Java 后,就是 public static final。如:
@file:JvmName("Utils")
package com.demo
const val score = 10;
外界调用时,可以直接使用 Utils.score,该值不能被修改。
kt 中无法定义出被 public static 修饰的属性。
可变参数
-
使用 vararg 修饰符修饰。
-
可变参数可定义在任意位置。
-
java 中可以将一个数组直接赋值给一个可变参数,但 kt 中数组必须使用 * 进行解包。
-
为 可变参数后面的形参进行赋值时,必须使用命名参数 —— 不然无法识别该实参是给可变参数的,还是给别的形参的,即使类型不同。
// 可变参数可以定义在任意位置
fun test(vararg a: Int, b: Int) {
val sb = StringBuilder()
for (index in 0..(a.size - 1)) {
sb.append("${a[index]} ")
}
sb.append(b)
println(sb.toString())
}
fun main(args: Array<String>) {
val a: IntArray = intArrayOf(1, 2, 3, 4)
test(*a, b = 5) // 数组必须被解包
}
中缀调用
使用 infix 关键字修改的函数为中缀函数。
调用中缀函数时可以省略点号和括号,而且中缀函数需要满足以下条件:
-
必须是成员函数或扩展函数。因为这样才能确定调用者的类型。
-
必须只有单个参数。
-
参数不能是可变参数,而且不能有默认值
-
接受者和参数都需要明确指定。即使接受者是 this ,也不能省略。
// 扩展函数的中缀
infix fun Any.test(a: Int) = println("${a * 2}")
class My {
// Any 为所有 kt 类的父类,该函数表示任意一个对象都可以调用 add 函数。
infix fun Any.add(s: String) = "${this}+${s}"
fun test(s: String) = this add s
// test2 与 test 效果一样
fun test2(s:String) = this.add(s)
}
fun main(args: Array<String>) {
val my = My()
println(my.test("xxx"))
println(my.test2("xxx"))
}
注意其中的 test 与 test2 两个函数,都没有省略 this。这是中缀函数要求的:不能省略 this。
局部函数
kt 中可以在函数中嵌套函数。
-
局部函数可以访问所在函数的所有参数和变量,但只能是 定义在局部函数前面的参数和变量。
fun test(){ val b = "name" fun name() = println("${b}") // 局部函数 val a = "age" // name 函数无法访问到该变量 name() }
-
局部函数可以定义在扩展函数中。
fun User.validate() { fun vali(name:String,value:String){ if (value.isEmpty()) { throw IllegalArgumentException("${name} 不能为空") }else{ println("正确的") } } vali("id",this.id) } fun main(args: Array<String>) { val u1 = User("","11") val u2 = User("2","22") u2.validate() u1.validate() // 该句报错 }
可空类的扩展
扩展函数的接收者类型可以为可空类型
定义成可空类型时,扩展函数中使用 this 可能为 null,所以需要单独判断。
fun main(args:Array<String>){
var t:Test? = Test()
t.test()
t = null
t.test()
}
class Test
// 类型使用 Test?,表示该方法可以被可空类型的对象调用
fun Test?.test() {
if (this == null) println("xxx") else println("$this")
}
成员扩展
在一个类中为另一个类定义扩展函数或属性,这样的函数或属性叫成员扩展
-
成员扩展不能在类的作用域之外使用
fun main(args: Array<String>) { Outer().test("12345678") // println("aabbcc".six()) 在类的外面无法访问在类中定义的扩展函数 six() } class Outer { fun String.six(): Char = toCharArray()[6] fun test(s: String) { println(s.six()) } }
-
无法像普通的扩展函数那样,在不改变类的情况下添加新的扩展。如上例,在不改变 Outer 类的情况下,无法为 String 在 Outer 类中定义新的成员扩展。