美文网首页
Kotlin基本篇-彻底理解匿名函数和高阶函数

Kotlin基本篇-彻底理解匿名函数和高阶函数

作者: 没走过的二丁目 | 来源:发表于2021-03-16 17:06 被阅读0次

    Kotlin 总结分享

    如果用一句话总结kotlin,那么就是:更好的java
    类型申明

    String a = "I an java";
    
    Val a :String = "I an Kotlin"
    

    为什么采用这种风格? 代码的可读性更好

    增强的类型推到

    val String = "I an Kotlin"
    val int = 12
    val long = 12L
    val float = 13.14f
    val double = 12.12
    val double = 1.0e2 // 100
    

    类型推导在很大程度上提高了开发效率,当我们使用kotlin的时候,不再需要写大量的类型

    声明函数 返回值类型

    fun sum(x:Int,y:Int):Int{
        return x+y
    }
    fun sum(x:Int,y:Int) = x+y
    

    val 和var的使用规则
    var代表了 变量,val 具有java中的final 关键字的效果,引用不可变,但是其引用的值是可以更改的

    val bool = Book() //用val声明的book对象的引用不可变 
    book.name = "kt"
    

    优先使用val 声明一个本身不可变的变量是一种防御性编码思维,更加安全可靠,不可变的变量意味着更加容易推理,越是复杂的业务逻辑,优势越大,但是如何保证引用对象的不可变?--- 参考集合

    高阶函数与lambda

    函数试语言一个典型的特征就在于 函数是头等公民 ---- 我们不仅可以像类一样在顶层直接定义一个函数,还可以在一个函数内部定义一个局部函数

    fun foo(x:Int){
            fun double(y:Int) = y*2
            print(double(x))
        }
    
    >>> foo(1)
    2
    

    此外,我们还可以直接将函数像普通变量一样传递给另一个函数,或在其他函数内被返回

    实例:函数作为参数的需求

    现有关于国家的数据集List<Country> 如何筛选出欧洲的国家?

    data class Country(
        val name:String,
        val continent:String,
        val population:Int
    )
    class  CountryApp{
        fun filterCountries(countries:List<Country>):List<Country>{
            val res = mutableListOf<Country>()
            for (c in countries){
                if(c.continent == "EU"){
                    res.add(c)
                }
            }
            return res
        }
    }
    

    需求变化一:不仅想要欧洲,还想要亚洲 如何修改

    fun filterCountries(countries:List<Country>,continent: String):List<Country>{
            val res = mutableListOf<Country>()
            for (c in countries){
                if(c.continent == continent){
                    res.add(c)
                }
            }
            return res
        }
    

    需求变化二:不仅需要欧洲,亚洲等,还需要筛选一些 具有人口规模的国家

    fun filterCountries(countries:List<Country>,continent: String,population: Int):List<Country>{
            val res = mutableListOf<Country>()
            for (c in countries){
                if(c.continent == continent && c.population>=population){
                    res.add(c)
                }
            }
            return res
        }
    

    如果按照现有的修改,更多的筛选条件会作为方法参数不断的累加,而且业务逻辑高度耦合,解决问题的核心在于对这个方法进行解耦合 java做法:传入一个类对象,根据不同的筛选需求创建不同的子类(貌似就是策略模式),对于 kotlin ,支持高阶函数,我们可以把筛选逻辑变成一个方法传入,这种思路更加简单

    为了了解高级特性,所以假如有一个新的测试类

    class CountryTest{
        fun isBigEuropeanCountry(country: Country):Boolean{
            return country.continent == "EU" && country.population>1000000
        }
    }
    

    调用isBigEuropeanCountry 方法就能够判断一个国家是否是一个人口超过百万的欧洲国家,那么怎么才能把这个方法变成 filterCountries 方法的一个参数呢?要解决以下两个问题
    · 方法作为参数传入,必须像其他参数一样具备具体的类型信息 (也就是说 需要一个函数类型)
    · 需要把isBigEuropeanCountry 的方法引用当作参数 传递给 filterCountries

    函数的类型

    格式:(Int)-> Unit
    左边是参数类型,右边是返回值类型

    (a:Int,b:Int) ->Int 
      (Int ,String) ->String
    (Int)->((Int) ->Unit)     // 返回类型是一个函数也是可以的
    

    有了函数类型,那么就可以修改filterCountries 方法了

    fun filterCountries(countries:List<Country>,filter:(Country)->Boolean):List<Country>{
            val res = mutableListOf<Country>()
            for (c in countries){
                if(filter(c)){
                    res.add(c)
                }
            }
            return res
        }
    
        fun isBigEuropeanCountry(country: Country):Boolean{
            return country.continent == "EU" && country.population>1000000
        }
    

    虽然已经改造了筛选方法 但是我现在已经有了一个筛选策略(isBigEuroupeanCountry)如何把这个函数传进去呢? 也许你想这么用

    filterCountries(countries, isBigEuropeanCountry) // 很遗憾这里第二个参数会报错 ,原因是类型不匹配
    //凌乱了,不是说 函数可以当成参数来传递吗,为啥这里不行
    

    方法和成员引用
    kotlin 存在一种特殊的语法,通过两个冒号来实现对于某个类的方法进行引用,假如我们有一个CountryTest类的实例对象 countryTest ,如果要引用他的isBigEuropeanCountry方法,就可以这么写

    countryTest::isBigEuropeanCountry
    

    在kotlin中函数是头等公民,那么怎么直接对方法引用呢

    ::isBigEuropeanCountry
    

    所以上面的使用我们就可以这样写

    filterCountries(countries, ::isBigEuropeanCountry) // 这里不会再报错
    

    经过这样的重构,程序显然比之前优雅许多,可以根据任意的筛选需求,调用同一个filterCountries 方法来获取国家数据。

    匿名函数

    继续思考一下筛选方法,每新增一个需求,就需要写一个新的筛选方法,但是很多都是零食性的,不需要被复用,于是匿名函数就派上用场,kotlin支持在缺省函数名的情况下,直接定义一个函数,所以isBigEuropeanCountry方法我们可以直接定义为

     fun (country: Country):Boolean{ // 没有函数名字
            return country.continent == "EU" && country.population>100000
        }
    

    当我们在编译器里这么写一个匿名函数 它会报错 Function declaration must have a name ,又凌乱了,不是可以定义匿名函数吗,为啥又要有名字,可见 匿名函数不是这么用的

    那这个匿名函数有啥用? 其实匿名函数是用来传递的,他的使用方式是这样

    filterCountries(countries, fun(country:Country):Boolean{ return country.continent == "EU" && country.population>100000})
    

    知道了匿名函数,我们再来看一下lambda是什么,由匿名函数可以知道,由于要传入的匿名函数 早已经在参数中声明了参数和返回值,那么匿名函数中的参数和返回值是不是可以也不要了

    fun filterCountries(countries:List<Country>?,filter:(Country)->Boolean):List<Country>{//这里可以推导出传入的参数和返回的类型
            val res = mutableListOf<Country>()
            for (c in countries!!){
                if(filter(c)){
                    res.add(c)
                }
            }
            return res
        }
    
        fun test(){
    
            filterCountries(countries, fun(country:Country):Boolean{ return country.continent == "EU" && country.population>100000})
            filterCountries(null, {country ->  country.continent == "EU" && country.population>100000 })
        }
    

    当我们只保留需要的内容 ,就形成了lambda表达式 {参数变量 -> 返回值}
    lambda 的表达是有自己的规则

    • 一个lambda必须通过{} 来包裹
    • 如果Lambda声明了参数部分的类型,且返回值类型支持类型推导,那么Lambda变量就可以省略函数类型声明
    val sum:(Int,Int) ->Int = {x:Int,y:Int ->x+y} // 完整写法
    val  sum = {x:Int,y:Int -> x+y} // 推导类型写法
    val sum:(x:Int,y:Int)->Int = {x,y ->x+y} // 省略参数部分类型
    
    • 如果Lambda 变量声明了函数类型,那么Lambda的参数部分类型就可以省略
    • 此外 Lambda表达式返回不是Unit,那么默认最后一行表达式的值类型就是返回值类型

    一个例子来看匿名函数,Lambda究竟是什么

    fun hello(int: Int)  = { // 用Lambda初始化了一个函数
       print(int)
    }
    hello(12) // 调用
    

    上述例子会打印12 吗 不会,因为foo(12)它不是函数,也就是说 Lambda 不是函数,他其实是一个对象,我们通过查看其Java代码

    private final Function0 hello(final int a) {
          return (Function0)(new Function0() {
             // $FF: synthetic method
             // $FF: bridge method
             public Object invoke() {
                this.invoke();
                return Unit.INSTANCE;
             }
    
             public final void invoke() {
                int var1 = a + TT2.INSTANCE.getCc();
                boolean var2 = false;
                System.out.print(var1);
             }
          });
       }
    

    通过把kotlin编译成Java代码发现 hello这个方法里返回了一个Function0 类的匿名内部类的对象,并且其内部有invoke方法,那么我们想让上述调用打印出12 应该这样写

    hello(12).invoke // 这个时候会打印出12 
    

    通过这个我们发现 匿名函数(简写 后成Lambda)都不是函数,而是对象,由于他们是对象,所以能在函数中当成参数传递,这也就是高阶函数的本质

    Function类型

    kotlin 在JVM层设计了Function类型(Function0 Function1 ... Function22)来兼容Java的Lambda表达式,后缀数字代表了Lambda参数的数量,每一个Function类型都有一个invoke方法

    中缀表达式

    kotlin中的中缀表达式有些什么?

    for (i in 1..10) print(i)   //[1,10]
    for( i in 1..10 step 2) println(i) // 1,3,5,7,9
    
     for (i in 10 downTo 1) print(" $i") // 倒叙
     for(i in 1 until  10) print(" $i") // [1,10)
    
     val array:IntArray = IntArray(10)
     for ((index,value) in array.withIndex()) // 数组index value 一起遍历
    

    以上这些奇特方法 如 in,step, downTo ,until 它们可以不通过点号,而是通过中缀表达式来被调用,从而让语法变得更加简洁直观。
    自定义一个中缀表达式

    class Person(val name:String,val age:Int)
    infix fun Person.vs(person: Person){
        when {
            age - person.age >0 -> {
                print("$name 比 ${person.name} 年长")
            }
            age == person.age -> {
                print("$name 和 ${person.name} 一样大")
            }
            else -> {
                print("$name 比 ${person.name} 小")
            }
        }
    }
    
    val zhansan = Person("张三",18)
    val lisi = Person("李四",19)
    val wangwu = Person("王五",18)
    zhansan vs lisi  //张三 比 李四 小
    

    如果把复杂条件写成中缀的形式,会让代码看起来特别简洁

    定义中缀函数的条件

    • 中缀函数必须是某个类型的扩展函数或成员方法
    • 中缀函数只能有一个参数 (有且只有一个参数)
      中缀的形式 A 中缀方法 B(比如 张三 vs 李四)
      由于to会返回Pair这种键值对的数据结构,因此我们经常会与map结合一起使用
    mapOf(1 to "one",2 to "two")
    

    字符串

    定义原生字符串 使用"""

     val c = """ \nAAA """   // 这里的\n 不会转义 定义html比较方便
     val a ="A\nA" // 
    val c = "A${}B"  // 字符串模版写法 {表达式}
    

    相关文章

      网友评论

          本文标题:Kotlin基本篇-彻底理解匿名函数和高阶函数

          本文链接:https://www.haomeiwen.com/subject/tjahcltx.html