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<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"  // 字符串模版写法 {表达式}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355

推荐阅读更多精彩内容

  • 1. Kotlin介绍 基于JVM。可以与Java进行混合开发和相互调用。由JetBrains开发。系出名门,也得...
    RobinYeung阅读 1,135评论 0 0
  • 第2章 Kotlin 语法基础 人与人之间通过语言来交流沟通,互相协作。人与计算机之间怎样“交流沟通”呢?答案是编...
    光剑书架上的书阅读 1,618评论 0 6
  • 前言 人生苦多,快来 Kotlin ,快速学习Kotlin! 什么是Kotlin? Kotlin 是种静态类型编程...
    任半生嚣狂阅读 26,209评论 9 118
  • Kotlin的优势 代码简洁高效、强大的when语法,不用写分号结尾,findViewById光荣退休,空指针安全...
    Windy_816阅读 1,288评论 1 6
  • 基础语法 1.1 增强类型推导 类型推导是Kotlin在java语言上的增强。编译器可以在不显示声明情况推导出类型...
    zcwfeng阅读 1,052评论 0 2