Android Kotlin(2)之《函数和Lambda表达式》
Android Kotlin第二篇,接下来让我们一起认识下kotlin里的函数和Kotlin新特性之一Lambda表达式。当然你深入了解Kotlin的时候,你会发觉Kotlin函数比java函数真的强大很多,能节省很多,也会让自己代码清晰很多。
Android Kotlin系列前面会先讲解Kotlin基础,后续才会有具体到Android内,目前是用Android做调试测试验证Kotlin
一、函数
1、函数声明
函数声明可以不带返回,也可以带返回,例如:
//带返回的声明
fun test0(a0 : Int) : Int{
return a0
}
//不带返回的声明,tes1函数等同于test2
fun test1(a1 : Int):Unit{
}
//不带返回的声明
fun test2(a2 : Int){
}
与java区别:
a、java声明,默认是私有的函数;Kotlin默认是公有的函数
b、Kotlin函数必须有fun关键字
c、java返回类型在函数名前;Kotlin返回类型在方法()后后面用“:”隔开,如上test0函数
2、函数调用
可以直接调用,例如:
FunGoKotlin().test0(1)
3、函数的参数
函数声明时参数,可以不带默认值参数,也可以带默认值参数,这里与java区别比较大,我觉得这点比java更加强大和灵活,例如:
//默认参数, b3 : Int = 10
fun test3(a3 : Int, b3 : Int = 10, c3 : Int = 10,d3 : String = "默认值") : Int{
return a3+b3
}
//调用默认参数
val a3_1 : Int = test3(1)//使用默认参数值
val a3_2 = test3(1,2);//不使用默认参数值
val a3_3 = test3(1,c3 = 3, d3 = "")//假设我不想穿其中某个参数(这个参数最后一位d3),后面回去前面在赋值的时候可以使用参数“参数名称=赋值”进行调用函数
这里声明函数的时候,函数参数可以带默认值“d3 : String = "默认值"”,调用的时候也可以使用“函数名(d3=“赋值”)”指定某个参数赋值
注意:
//继承复写方法,override 关键字函数参数是不能有默认值的
open class A {
open fun foo(i: Int = 10) { }
}
class B : A() {
override fun foo(i: Int) { } // 不能有默认值
}
与java区别:
与java最大区别就是,Kotlin是可以对参数重命名,并且可以调用参数重命名进行赋值,而且函数的参数是可以带有默认值的
4、单表达式函数
当函数返回单个表达式时,可以省略花括号并且在 = 符号之后指定代码体即可,例如;
//单表达式函数
fun test4(x : String) = "单表达式函数" + x
fun test4(x : Int,y : Int) = x + y
这点比java强大
5、中缀标示方法(全新特性)
这个是java所没有的特性
函数还可以用中缀表示法调用,当
- 他们是成员函数或扩展函数
- 他们只有一个参数
- 他们用 infix关键字标注
让我们看个具体例子,这样会更加好懂一些。例如:
//中缀标示方法,必须要带参数才行,而且如果只有一个参数该参数是不能设置默认值,关键字:infix
infix fun Int.test5_1(x : Int) = x+1
infix fun Int.test5_2(x : Int) : Int{ return x * 2 }
infix fun String.test5_2(x : Int) : Int{ return x * 2 }
fun test5(x : Int) : Int{
when(x){
//中缀调用如下,总结是有两种:“xx 方法 传值”,“xx.方法(传值)”
//这里提前用到了when控制流
//也提前用到了retrun返回,如果在多个控制流里,具体如果返回不加“@”,默认是返回最近的一层控制流,“@”可以指定返回到某层
0 -> return@test5 1 test5_2 4
1 -> return@test5 2 test5_2 4
2 -> return@test5 1.test5_2(4)
3 -> return@test5 "" test5_2 6
4 -> return@test5 "".test5_2(6)
else -> return@test5 0
}
}
在这里你可以更加自己的需求,扩展一些自己常用的方法
6、可变数量的参数(vararg)
参数前加上vararg,参数数量就可以是变化的。因为上面所说的参数特性,参数是可以重新命名且指定赋值,所以使得其可变数量的参数比java更加强大,那么强大在何处呢,如下:
java这样声明是不可以的
public void test0(String a,String... b,String c){
}
但是Kotlin这样声明完全可以
//可变数量的参数,关键字varargs
//一个函数里只能有一个vararg
fun test6(a : String, vararg b : String, c : String) : String{return ""}
//调用
val log : String = test6(topFun(),"a0","a1",c = "ad")
val log1 : String = test6(topFun(),"a0","a1",c = "ad")
7、函数的作用域
在 Kotlin 中函数可以在文件顶层声明,这意味着你不需要像一些语言如 Java、C# 或 Scala 那样创建一个类来保存一个函数。此外除了顶层函数,Kotlin 中函数也可以声明在局部作用域、作为成员函数以及扩展函数。
函数可以在文件顶层声明,也就是说在文件里函数不一定要放在类里,可以放在外面和package一级,例如:
package com.xiaoqiang.kotlin.base
//在 Kotlin 中函数可以在文件顶层声明,这意味着你不需要像一些语言如 Java、C# 或 Scala 那样创建一个类来保存一个函数
fun topFun() : String{
return "aa"
}
class FunGoKotlin {
//调用顶层函数
val test7 = topFun()
}
局部函数(新特性)
//Kotlin 支持局部函数,即一个函数在另一个函数内部
fun test6() : String{
fun test66() : String{
return "test66()"
}
val log : String = test6("a", "b0","b1",c = "ad")
return log+","+test66()
}
我感觉这个还是有点用处,可以是代码逻辑更加清晰,增加代码可读性
8、支持泛型函数
这个和java差不多,唯一区别就是要在前面尖括号里指定泛类型
//函数可以有泛型参数,通过在函数名前使用尖括号指定。
fun <T> test8(t : T) : T{
return t
}
fun <L> test8(l : List<L>) : List<L> {
return l
}
9、支持尾递归函数
//kotlin是支持尾递归函数的,这可以允许一些算法可以正常的使用循环而不是写一个递归函数,而且没有内存溢出的风险。
// 如果一个函数用tailrec修饰符标记就满足了编译器优化递归的条件,并用高效迅速的循环代替它。
//当函数被关键字tailrec修饰,同时满足尾递归(tail recursion)的函数式编程方式的形式时,编译器就会对代码进行优化,
// 消除函数的递归调用, 产生一段基于循环实现的, 快速而且高效的代码。
tailrec fun findFixPoint(x: Double = 1.0): Double
= if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))
二、Lambda表达式与高阶函数
当你学习了Lambda表达式后,你会发现Lambda表达式真的是一种非常简单的方法,去定义一个匿名函数,比java能节省很多代码,当然越简单也许理解起来越困难,但是当你理解了后,你会发现真的很好用,它能避免我们去写一些包含了某些函数的抽象类火灾接口,然后在类中去实现它们。在Kotlin,我们把一个函数作为另外一个函数的参数。
1、lambda表达式
Lambda表达式的完整语法形式,例如下:
val sum = { x: Int, y: Int -> x + y }
lambda表达式总是被大括号扩着,参数声明在大括号内,函数体在 ->符号之后。
其实这个调用起来更像是函数,直接sum(x,y),所有Kotlin允许我们这样来写Lambda表达式:
val sum1 : (Int, Int) -> Int = { x, y -> x + y }
这样看起来和函数很像
那么对应的返回值呢?有是什么。如果返回值类型是Unit,可以
val sum2 : (Int,Int) -> Unit = {x,y -> Unit}
如果返回值类型确定不是Unit,默认返回值是 -> 后函数体中最后一个表达式视为返回值,例如:
val sum3 : (Int, Int) -> Int = { x,y ->
val temp = x + y
temp * 2//这个视为最后返回值
}
单个参数的隐式名称(it)
当然Kotlin还给我们提供了特殊约定,特殊情况下还可以进一步减少代码,如果Lambda表达式只有一个参数值时,我们可以使用it来代替单个参数,视为隐式名称,例如;
val sum4 : (Int) -> Int = { it * 10 }
看到这里,我想你会发现Lambda表达式基本上没有指明返回类型,而是自动推断出类型。如果你想指明返回类型的话,你可以使用匿名函数
匿名函数
匿名函数,没有名字,其他语法和常规函数类似。举个具体实例吧:
//匿名函数
val test13 = fun(x : Int, y : Int):Int { return x + y }
//调用匿名函数
val test14 = test13(1,2)
看到这里,我相信大家对lambda表达式有了一定了解
2、高阶函数
高阶函数是将函数用作参数或返回值的函数。我觉得这个非常有用,而且很好理解,有一个例子很好解释:
fun <T> lock(lock: Lock, body: () -> T): T {
lock.lock()
try {
return body()
}
finally {
lock.unlock()
}
}
body拥有函数类型, 所以它应该是一个不带参数并且返回 T类型值的函数。 它在 try-代码块内部调用、被 lock保护,其结果由lock()函数返回。可能刚刚开始比较难理解,我们还是一起看看几个实例,会让我们更加好理解一些,例如:
实例一
//接受另一个函数作为参数的函数,我们必须为该参数指定函数类型
fun <T> max(collection: Collection<T>, less: (T, T) -> Boolean): T? {
var max: T? = null
for (it in collection)
if (max == null || less(max, it))
max = it
return max
}
less就是你要传递的函数参数,那么如何使用呢,你不要小瞧上面的函数哦!接下里让我看看有哪些使用:
fun test10(a : Int) : String{
val numbers : List<Int> = mutableListOf(17,12,5,67,3,10)
var stings : List<String> = arrayListOf("asd","dd","a","dddd")
var times : List<String> = arrayListOf("15:00:01","15:00:00","4:10:00","17:00:00")
fun dateScale(date1 : String, date2 : String) : Boolean{
val df = SimpleDateFormat("HH:mm:ss")//创建日期转换对象HH:mm:ss为时分秒,年月日为yyyy-MM-dd
val dt1 = df.parse(date1)//将字符串转换为date类型
val dt2 = df.parse(date2)
if (dt1.getTime() > dt2.getTime())
//比较时间大小,如果dt1大于dt2
{
return true
}else{
return false
}
}
when(a){
0 -> {
//求Int集合里最大值,返回最大值
return@test10 max(numbers, {x,y -> x>y}).toString()
}
1 -> {
//求Int集合里最小值,返回最小值
return@test10 max(numbers, {x,y -> x<y}).toString()
}
2 -> {
//求字符串集合里长度最长的值,返回长度最长的字符
return@test10 max(stings, {x,y -> x.length>y.length}).toString()
}
3 -> {
//甚至你可以把你需要做的一些操作放在一个函数里进行调用,例如:dateScale(x,y)
//求时间集合里最小时间,返回最小时间
return@test10 max(times, {x,y -> dateScale(x,y)}).toString()
}
//当然你还可以实现很多很多,是不是感觉比java更加简洁和易懂啊,而且非常非常的节省代码,说实话我是喜欢上Kotlin风格
else -> return@test10 ""
}
}
哈哈,有没有发现真的比java能节省很多代码,如果你想用java实现上面方法,当然也可以实现,只是你会发现java没有Kotlin把代码分析的如此清晰,其中max方法你完全不用改变
示例二
上面返回的是单个值,那么我是否可以写个公共函数用来返回满足条件的集合呢,当然可以,只需要对max稍作修改,如下:
fun <T> meetCons(collection: Collection<T>, less: (T) -> Boolean): List<T?> {
val tmp: MutableList<T?> = mutableListOf()
for (it in collection)
if (less(it))
tmp.add(it)
return tmp
}
这样,你就可以对集合进行指定筛选出你需要的集合了,调用:
fun test11() : String {
val stings : List<String> = arrayListOf("asd","dd","ass","dddd","125")
//返回所有字符长度为3的集合
//你可以这样返回
// return meetCons(stings,less = {x -> x.length == 3}).toString()
//当然你也可以省去less =
return meetCons(stings){x -> x.length == 3}.toString()
}
示例三
上面我将集合作为参数进行传递,那么是否可以对集合进行添加类似扩展函数呢,答案是肯定。
我们将Collection<T>拿到方法名前,就相当于对Collection<T>进行扩展了,当然返回值啥地你都可与自由的扩展了,是不是觉得很神奇和好用,例如:
fun <T> Collection<T>.meetCons(less: (T) -> Boolean): Collection<T?> {
val tmp: MutableList<T?> = mutableListOf()
for (it in this)
if (less(it))
tmp.add(it)
return tmp
}
那么如何调用呢,和扩展函数类似调用方法,例如;
fun test12() : String {
val stings : List<String> = arrayListOf("asd","dd","ass","dddd","125")
//闭包
var sum = ""
stings.filter { it.length == 3 }.forEach {
sum += it
}
//返回所有字符长度为3的集合
//我们可以这样返回
// return stings.meetCons{x -> x.length == 3}.toString()
//当然只有一个参数的时候,kotlin是有个约定的,使用it代替单个参数,这样更加简洁
return stings.meetCons{ it.length == 3 }.toString() + ">>" + sum
}
上面调用里有闭包调用,Lambda 表达式或者匿名函数可以访问其 闭包 ,即在外部作用域中声明的变量。 与 Java 不同的是可以修改闭包中捕获的变量
匿名函数
匿名函数,没有名字,其他语法和常规函数类似,例如:
//匿名函数
val test13 = fun(x : Int, y : Int):Int { return x + y }
//调用匿名函数
val test14 = test13(1,2)
//匿名函数可以省略返回类型,类型会自动推断
val test15 = fun(x : Int, y : Int) = x + y
内联函数(inline、noinline)
使用高阶函数会带来一些运行时的效率损失:每一个函数都是一个对象,并且会捕获一个闭包。 即那些在函数体内会访问到的变量。 内存分配(对于函数对象和类)和虚拟调用会引入运行时间开销。
但是在许多情况下通过内联化 lambda 表达式可以消除这类的开销
//内联函数
inline fun test16(inlined: () -> Unit, noinline notInlined: () -> Unit) {
// ……
}
inline 修饰符影响函数本身和传给它的 lambda 表达式:所有这些都将内联到调用处。
内联可能导致生成的代码增加,但是如果我们使用得当(不内联大函数),它将在性能上有所提升,尤其是在循环中的“超多态(megamorphic)”调用处。
使用inline可以是函数内联,noinline 可以控制局部不内联,调用合适会在性能上有所提升,写代码的时候可以考虑这些,优化代码
源码下载
这里源码会随着后面发布的Kotlin逐渐完善