Kotlin基础语法----高阶函数和Lambda

Kotlin语言天然支持了部分函数式特性。函数式语言一个典型的特性在于函数是头等公民–我们不仅可以像类一样在顶层直接定义一个函数,也可以在一个函数内部定义一个函数,如下所示:

fun foo(x:Int) {
    fun double(y:Int):Int {
        return y+2
    }
    println(double(x))
}

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

1.抽象和高阶函数

所谓的高阶函数,我们可以把它理解成“以其他函数作为参数或返回值的函数”,高阶函数是一种更加高级的抽象机制,它极大地增强了语言的表达能力。

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

下面通过一个实例来引入:

小明喜欢旅游,写了一个记录世界各国的程序

data class Country(val name: String, val area: String, val population: Int)

他现在想筛选出所有的欧洲国家,因此他定义了一个方法

class CountryApp {
    fun filterCountries(countryList: List<Country>): List<Country> {
        val res = mutableListOf<Country>()
        for (c in countryList) {
            if (c.area == "EU") {
                res.add(c)
            }
        }
        return res
    }
}

设计完小明又想了想,万一之后他想去非洲呢?因此,他改动了一下程序,如下:

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

小明喜欢去人口众多的国家,因此他改写了程序,能筛选出人多的某个大洲的国家,如下:

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

设计完此代码之后,小明突然想到,如果再增加其他的筛选条件呢,可咋整呢?解决问题的突破口就是对filterCountries进行解耦,关键在于我们是否能把筛选的逻辑行为抽象成一个参数,传统的做法是传入一个类对象,但是在kotlin里,因为支持高阶函数的缘故,理论上我们可以把筛选的逻辑变成一个方法来传入,这种思路更加简单。

class CountryTest {
    fun isBigEuCountry(country: Country): Boolean {
        return country.area == "EU" && country.population >= 10000
    }
}

isBigEuCountry()方法能够判断人口大于等于10000的欧洲国家,那如何把此方法变成filterCountries方法的一个参数呢?要实现这一点,必须先解决如下的两个问题:
(1)方法作为参数传入,必须像其他参数一样具有具体的类型信息
(2)需要把isBigEuCountry方法的引用作为参数传递给filterCountries方法

3.函数的类型

kotlin中的函数类型声明需要遵循以下几点:
(1)通过->符号来组织参数类型和返回值类型,左边是参数类型,右边是返回值类型
(2)必须用一个括号来包裹参数类型
(3)返回值即使是Unit,也必须显示声明

(Int) ->Unit
()->Unit
(Int,String) ->Unit
(errorCode:Int,desc:String?) ->Unit
((errorCode:Int,desc:String?)->Unit)?
(Int)->((Int)->Unit)
(Int)->(Int)->Unit
((Int)->Int)->Unit

学习了上述函数类型之后,filterCountries方法就可以再次改造一下了:

fun filterCountries(countryList: List<Country>, map: (Country) -> Boolean): List<Country> {
        val res = mutableListOf<Country>()
        for (c in countryList) {
            if (map(c)) {
                res.add(c)
            }
        }
        return res
    }

那如何将之前定义的isBigEuCountry传入呢?那需要了解一下方法引用表达式了。

4.方法和成员引用

ko’t’lin存在一种特殊的用法,通过两个冒号来实现对某个类的方法进行引用。

 val countryA = Country("德国", "EU", 10000)
    val countryB = Country("法国", "EU", 5000)
    val countryC = Country("英国", "EU", 6000)
    val list = mutableListOf(countryA, countryB, countryC)
    val countryApp = CountryApp()
    val countryTest = CountryTest()
    countryApp.filterCountries(list, countryTest::isBigEuCountry)

我们还可以通过这种语法来定义一个类的构造方法引用变量。

data class Book(val name:String)

val getBook=::Book
    println(getBook.invoke("《战争与和平》").name)
    println(getBook("《骆驼祥子》").name)

此时,getBook的类型就是(String)->Book了。类似的道理,如果我们要引用某个类中的成员变量,则可以着这样使用:

 val bookNames= listOf(
        Book("Java"),
        Book("Kotlin")
    ).map(Book::name)
    println(bookNames)

5.匿名函数

kotlin支持在缺省函数名的情况下,直接定义一个函数,所以上述的isBigEuCountries可以直接定义成:

fun(country:Country):Boolean {
    return country.area=="Eu" && country.population>=10000
}
 countryApp.filterCountries(list,fun(country:Country):Boolean {
        return country.area=="Eu" && country.population>=10000
    })

在实际使用中,例如这种筛选需求可能都是临时的,因此通过匿名函数的方式进一步优化了使用。

6.Lambda表达式

Lambda表达式简单一点理解就是简化表达后的匿名函数,实质上它是一种语法糖。
现在回头看看上述定义的filterCountries中的匿名函数,可以发现:
(1)fun(country:Country)比较啰嗦,所以只要一个代表变量的country就行了
(2)return关键字也可以省略,这里返回的是一个有值的表达式
(3)模仿函数类型的语法,我们可以用->把函数和返回值连接在一起。再次简化代码,可以这样写:

countryApp.filterCountries(list,{country -> country.area=="Eu" && country.population>=10000 })

这就是Lambda表达式,它与前面的匿名函数一样,是一种函数字面量。

既然Lambda表达式如此牛逼,那我们就需要学习一下Lambda表达式的具体用法。
现在使用Lambda表达式定义一个减法操作:

val sub: (Int, Int) -> Int = { x: Int, y: Int -> x - y }

由于类型推导,可以简化为:

val sub2: (Int, Int) -> Int = { x, y -> x - y }
val sub3 = { x: Int, y: Int -> x - y }

总结一下Lambda的用法:
(1)一个Lambda表达式必须通过{}来包裹
(2)如果Lambda声明了参数部分的类型,且返回值类型支持类型推导,那么Lambda变量就可以省略函数类型声明
(3)如果Lambda函数声明了函数类型,那么Lambda的参数部分的类型就可以省略
除此之外,如果Lambda表达式返回的不是Unit,那么默认最后一行表达式的值类型就是返回值类型。

Lambda表达式学到目前感觉是顺风顺水,如果我们用fun关键字声明Lambda表达式又会怎么样?try

fun test(num: Int): () -> Unit = {
    println(num)
}

我们在遍历集合时候使用:

listOf(10,20,30).forEach { test(it) }

这边的it是kotlin简化Lambda表达式的一种语法糖,叫做单个参数的隐式名称,代表Lambda接收的单一参数。

2.Function类型

Kotlin在JVM层设计了Function类型(Function0、…Function22)来兼容Java的Lambda表达式,其中的后缀数字代表了Lambda参数的数量,上述的test函数构建的其实是一个无参Lambda,所以对应的接口是Function0。

3.invoke方法
上述的test函数返回的类型是Function0.。我们调用test(n)只是构造了一个Function0对象,只有再调用Function0的invoke方法才能调用过程本身。

 listOf(10, 20, 30).forEach { test(it).invoke() }

如果觉得invoke这种方式不简洁,可以使用熟悉的括号来替代。

7.函数、Lambda和闭包

fun声明函数、Lambda表达式的语法容易产生混淆,我们需要通过总结以达到更好的区分:

(1)fun在没有等号,只有花括号的情况下,就是我们最常见的代码块函数体,如果返回非Unit值,必须带return。
(2)fun带有等号,是单表达式函数体,该情况下可以省略return。
(3)不管是val还是var,如果是等号加花括号的语法,那么构建的就是一个Lambda表达式,Lambda的参数在花括号内部声明。如果左侧是fun,那么就是Lambda表达式函数体,也必须通过()或者invoke来调用Lambda。

匿名函数体、Lambda表达式、局部函数、object表达式在语法上都有一对"{}",由这对花括号包裹的代码块如果访问了外部环境变量则被称之为一个闭包。一个闭包可以被当做参数传递或者直接使用,它可以简单地被看做是“访问外部环境变量的函数”。Lambda表达式就是典型的闭包形式。
与Java不同的是,kotlin中的闭包不仅支持访问外部变量,还能够对其进行修改。

Lambda表达式还需要补充的就是,如果一个函数只有一个参数,且该参数为函数类型,那么在调用该函数时,外面的括号就可以省略。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值