kotlinAndroid知识Kotlin开发指南

Android Kotlin(2)之《函数和Lambda表达式》

2017-07-06  本文已影响201人  小强彬

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 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逐渐完善

上一篇下一篇

猜你喜欢

热点阅读