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

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 如何筛选出欧洲的国家?

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

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

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

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

fun filterCountries(countries:List,continent: String,population: Int):List{
        val res = mutableListOf()
        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,filter:(Country)->Boolean):List{
        val res = mutableListOf()
        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?,filter:(Country)->Boolean):List{//这里可以推导出传入的参数和返回的类型
        val res = mutableListOf()
        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基本篇-彻底理解匿名函数和高阶函数)