函数
变量的写法和Java顺序和关键字不一样,函数的写法也和Java存在类似的差距
private fun funName(name:String):Int
private/public -> 可见性修饰符
这个参数是可以省略的,默认情况下可见性为public
fun -> 函数声明关键字
跟变量的val/var对应,定义函数是通过fun关键字声明的,就可知道后面的命名是一个可执行函数
funName -> 函数名
自定义的函数名,根据函数功能和驼峰命名用户自定义
fun `fun`(){}
fun main() {
`fun`()
}
kotlin和Java的关键字有区别,为了实现互调,避免冲突,可以通过反引号定义函数名,将关键字进行进行声明调用
(name:String) -> 函数参数
参数的写法和变量的声明方法类似,变量名在前,类型在后,通过 : 进行连接
fun funName(name:String,age :Int = 18):Int {
return 0
}
fun test() {
funName("鼻涕粑粑")
funName("鼻涕粑粑",30)
// funName(30,"")
funName(age = 30,name = "鼻涕粑粑")
}
默认参数:参数声明的时候,可以通过 = 赋值默认参数,在函数调用的时候,可以不传入值参进行调用,赋值覆盖默认参数
具名参数:函数调用需要根据参数的声明顺序进行传参,kotlin中可以使用具名参数,在调用时通过 "参数名="的方式进行赋值,可以不用管值参的顺序
:Int -> 返回类型
函数的返回类型也是写在声明的后面,跟变量声明一样,通过 : 进行连接
Java中函数不返回任何类型,使用void进行声明,而在kotlin中,返回类型就是Unit,这种函数叫做Unit函数
fun funName(name:String,age :Int = 18):Unit {}
除了没有类型参数的返回类型Unit,kotlin中还有一种函数返回类型,Nothing,有一个TODO()方法,返回的参数就是Noting,这个函数的作用就是抛出异常,什么都不会返回,连一个Unit都不给你
@kotlin.internal.InlineOnly
public inline fun TODO(): Nothing = throw NotImplementedError()
匿名函数
函数定义的时候需要给函数定义名称,以便该函数在其他地方通过类名+函数名进行调用,kotlin中可以定义的时候不指定函数名,这样的函数就是匿名函数;
没有函数名就不能正常的通过函数名进行调用执行,那就需要一个载体去执行,匿名函数的存在形式可以理解为一种变量类型,跟基本类型功能一致,那么函数类型可以直接赋值给变量,也可以定义在函数的参数中,也可以作为返回值
val funType: () -> Unit = {} //不带参数,没有返回值
val funType1: () -> Int = {
1
} //不带参数,有返回值
val funType2: (Int) -> Int = {
it * 2
} //一个参数,有返回值,用默认值it指代传入的单参数
val funType3: (Int) -> Int = { num ->
num * 2
} //一个参数,有返回值,自定义参数名
val funType4: (Int, Int) -> Int = { paramFirst, paramSecond ->
paramFirst + paramSecond
} //多个参数,有返回值,多参数命名指定
fun main() {
funType()
funType2(1)
funType4(1,1)
}
函数类型
跟定义变量一样,变量类型通过 :进行连接,上面的 () -> Unit;() -> Int;(Int) -> Int;(Int, Int) -> Int;这些都是变量的函数类型,有了函数类型和变量名,就跟其他变量一样可以进行赋值和传递
函数参数和it关键字
既然是函数,就是有参数和返回值,匿名函数可以不带参数,也可以带一个或多个参数,参数写在函数类型定义的()体内,我们只需要指定参数的类型,参数名不需要在()体内一起声明,而是放在函数的具体定义{}中,如果在单参数的情况下,参数命可以不用显示指明,默认会用it去作为参数名,当然也可以自己手动指定,如果是多参数,it就不能指代首个参数名,需要使用就必须指明两个参数名
函数体和返回值
赋值通过={},具体的函数执行逻辑就写在{}中,如果在有参数的情况下,指定参数名后,在函数体内通过 -> 进行连接,->后就可以使用指定的参数,跟具名函数的返回参数需要使用return不一样,匿名函数通常情况下不用return进行返回数据,匿名函数会自动返回函数体的最后一行语句的结果
类型推断
跟基本类型一致,变量在不指定类型的情况下,通过赋值操作,kotlin会根据赋值类型进行变量的类型推断
val funType = {} //() -> Unit
val funType1 = {age :Int -> 18} //(Int) -> Int
val funType2 = {age :Int,name:String -> "鼻涕粑粑"} //(Int,String) -> String
通过直接匿名函数的赋值,变量的函数类型会根据是否有参数和最后一行的返回类型进行函数类型的推断,跟指定类型的情况不同,在推导情况下的函数体内,参数的类型和参数名都必须有,以便在函数体内进行操作和类型推断
参数是函数的函数
除了上面直接将匿名函数进行变量赋值,通过变量名直接调用以为,跟其他的类型变量一直,函数类型的变量也可以定义在函数的参数内
fun funTypeParamFun(name:String,age:Int,doSomething:(Int,String) -> Unit){
doSomething(1,name)
}
fun main() {
val funParam ={age:Int,name:String ->
//doSomething
}
funTypeParamFun("鼻涕粑粑",30,funParam)
funTypeParamFun("鼻涕粑粑",30,{ param1,param2-> })
}
为了方便表达,参数是函数的函数确实比较拗口,我们将你们函数称为lambda,将他的定义叫做lambda表达式,返回的数据就是lambda结果,为啥叫lambda,因为在定义匿名函数的时候,使用了lambda演算记法
函数的定义,可以使用lambda参数,在函数体内,直接通过参数名进行调用,传入相对应类型的参数,然后在函数调用的时候,传入对应类型的具名函数或者直接传入一个lambda函数体就行
如果函数的lambda参数排在最后,或者是惟一的参数,就可以将表达式写在括号外,没有参数可以省略
fun funTypeParamFun1(doSomething:(Int,String) -> Unit){
doSomething(1,"鼻涕粑粑")
}
funTypeParamFun1{param1,param2->
}
fun funTypeParamFun(name:String,age:Int,doSomething:(Int,String) -> Unit){
doSomething(1,name)
}
funTypeParamFun("鼻涕粑粑",30){param1,param2->
}
这样就是我们比较常见的简略写法了,看起来像一个函数方法,其实()外的那部分是函数的最后一个lambda参数
内联函数
inline关键字是我在最早看Android开发从Java转kotlin的文章里面没有提到的,上面提到的匿名函数,可以让编码变得更加的灵活,这种灵活其实是有一定的开销的,对于JVM来说,我们定义的lambda会以对象实例的形式存在,会为所有同lambda打交道的变量分配内存,这就会产生内存开销,lambda的内存开销会带来严重的性能问题,为了让这种灵活性更加的高效,kotlin给出了一种优化方案,内联函数,通过给函数定义加上inline内敛函数关键字,内联函数作为参数赋值给lambda定义的时候,对应的函数就不会分配内存,编译器就会将对应的函数体内容复制粘贴到lambda的地方,等于在调用的地方直接执行内联函数的内容
//举个栗子
//定义了一个内联函数,函数参数是一个匿名函数
inline fun inlineFun(showName: () -> String) = println(showName())
//对比一个普通函数,函数参数同上是一个匿名函数
fun normalFun(showName: () -> String) = println(showName())
fun testInline() {
var name = "鼻涕粑粑"
//调用内联函数,传入一个lambda表达式
inlineFun { name }
//调用普通函数,传入同样的lambda表达式
normalFun { name }
}
上面这段kotlin代码,通过编译的Java代码就能很直观的理解为什么说inline关键字不会造成额外的内存开销
public static final void inlineFun(@NotNull Function0 showName) {
...
Object var2 = showName.invoke();
...
System.out.println(var2);
}
public static final void normalFun(@NotNull Function0 showName) {
...
Object var1 = showName.invoke();
...
System.out.println(var1);
}
public static final void testInline() {
final ObjectRef name = new ObjectRef();
name.element = "鼻涕粑粑";
// inlineFun { name }
String var3 = (String)name.element;
System.out.println(var3);
//normalFun { name }
normalFun((Function0)(new Function0() {
// $FF: bridge method
public Object invoke() {
return this.invoke();
}
@NotNull
public final String invoke() {
return (String)name.element;
}
}));
}
省略一些不重要的东西,直观的可以看到,普通函数和内联函数的实际定义的执行逻辑都是调用System.out.println("")打印传入的内容,而在使用了inline关键字的内敛函数,编译成Java后,直接将其中的打印语句复制粘贴到了调用内联函数的地方,而没有使用inline关键字的方法是通过一个桥接方法,new了一个Function对象,然后执行其中的invoke方法,这样就多产生了对象,多分配了内存
那是不是所有的函数都需要使用inline关键字去进行优化,内联函数可能会导致生成的代码增加,所以在使用中避免内联过大的函数,一般用在对高阶函数作为参数和具体化的类型参数时。
函数引用
除了使用lambda表达式将函数作为参数进行赋值,kotlin还支持传递函数的引用,函数的引用就可以将具名函数当做值参传递,但是需要具名函数的参数类型和返回值和定义的函数类型参数一致
//带有函数参数类型的具名函数
fun showName(name:String,addAge:(String)->String){
println(addAge(name))
}
//和lambda表达式参数返回值一致的具名函数
fun becomeYounger(name:String):String{
return "$name is young man"
}
fun testFun(){
//使用lambda表达式传函数参数
showName("鼻涕粑粑"){name->
"$name is young man"
}
//直接通过::符号将具名函数的引用当参数传递
showName("鼻涕粑粑", ::becomeYounger)
}
函数类型返回值
除了将函数类型当变量和形参定义之外,函数类型还可以当做一个函数的返回值类型,就是基本类型能用到的方式函数类型也可以支持的
//返回值是一个(String) -> String的函数
fun returnFunType():(String) -> String{
return {
"$it is the param"
}
}
fun testFun(){
//funReturn就是(String) -> String的函数
val funReturn = returnFunType()
//调用函数,传入String的参数即可
funReturn("鼻涕粑粑")
}
高级函数
对匿名函数有了了解过后,经常也在网上看到文章提到高级函数的概念,高级函数其实就是能接受函数作为参数,或者能讲函数作为返回值的函数,就叫做高级函数。
写在最后
这篇文章就是对学到的匿名函数和在开发中的应用的一个总结,kotlin编程对lambda表达式的应用还是非常的广,后面会记录标准库的扩展函数其实就是对本文内容的一个应用,举一个在其他文章经常看到的例子,在Java中,没法将函数作为参数传递给另外一个函数,就需要通过接口和匿名内部类去实现,控件的点击事件就是这样的,需要编写重复的模板代码,lambda的支持让开发能少些更多的模板代码,更加灵活的写代码.
//Java的点击事件,通过匿名内部类回调
new TextView(getApplicationContext()).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int id = v.getId();
}
});
//kotlin直接传递一个匿名函数,
TextView(appContext).setOnClickListener { v ->
val id = v.id
}
总结记录写文章真是一件麻烦的事情,又怕自己忘记,记录一下后期回顾再捡这些相关的点应该会快一些,keep going!!!