Kotlin 进阶之路(五) Lambda 编程

Kotlin 进阶之路(五) Lambda 编程

5.1 Lambda 表达式入门

  • Lambda 表达式介绍

1 无参数有返回值

定义无参数有返回值的 Lambda 表达式,只需要将函数体写在 {} 中,函数体可以是表达式或语句块

调用语法格式

{函数体}()

fun main(args: Array<String>) {
	{
		println("Lambda表达式无参数有返回值")
	}()
	//Lambda表达式无参数有返回值

2 有参数有返回值

定义有参数有返回值的 Lambda 表达式,需要指定参数名称以及参数类型,参数之间使用英文 , 分割,且参数类型可以省略,函数体会自动校对。Lambda 表达式中的 -> 用于表示箭头,用于指定参数或数据指向。

调用语法格式

{参数名: 参数类型, 参数名: 参数类型 … -> 函数体}(参数1, 参数2, …)

fun main(args: Array<String>) {
    val sum = {a : Int, b : Int -> a+b}(6,8)
    println("sum=$sum")//14
	var sum2 = {a : Int, b : Int -> a+b}
    println("sum=" + sum2(6,8))//14
}

  • Lambda 表达式返回值

Lambda 表达式省略了返回值的类型和方法名,接下来分析是如何声明返回值的类型和返回值?

fun main(args: Array<String>) {
    println("-----------------------1------------------------")
    val result1 = {
        println("输出语句1")
        "字符串"}()
    println("返回值: $result1")
    println("返回值类型: ${result1.javaClass}")
    println("-----------------------2------------------------")
    val  result2 = {
        println("输出语句1")
        println("输出语句2")
        18
    }()
    println("返回值: $result2")
    println("返回值类型: ${result2.javaClass}")
    println("-----------------------3------------------------")
    val  result3 = {
        println("输出语句1")
        println("输出语句2")
        true
    }()
    println("返回值: $result3")
    println("返回值类型: ${result3.javaClass}")
    /*-----------------------1------------------------
    输出语句1
    返回值: 字符串
    返回值类型: class java.lang.String
    -----------------------2------------------------
    输出语句1
    输出语句2
    返回值: 18
    返回值类型: int
    -----------------------3------------------------
    输出语句1
    输出语句2
    返回值: true
    返回值类型: boolean*/
}

从结果可以得出,在每次调用 Lambda 表达式时,不管方法体里面的语句执行多少条,返回值的类型和值都是由方法体中最后一条语句决定的。

5.2 高阶函数的使用

Lambda 表达式作为函数的实际参数或返回值使用时,成为高阶函数

  • 函数作为参数使用
fun IntRange.pickNum(function: (Int) -> Boolean) : List<Int>{
    val resultList = mutableListOf<Int>()
    for(i in this){                 //this 指向定义的区间(IntRange)范围1~20
        if(function(i)){            //判断传递过来的Lambda表达式是否满足条件
            resultList.add(i)       //符合条件的数据添加到集合中
        }
    }
    return resultList
}

fun main(args: Array<String>) {
    val list = 1..20
    println("---------能被5整除的数-----------")
    println(list.pickNum({ x: Int -> x % 5 == 0}))
    println("---------能被10整除的数-----------")
    println(list.pickNum({ x : Int -> x % 10 == 0}))
	/*---------能被5整除的数-----------
	[5, 10, 15, 20]
	---------能被10整除的数-----------
	[10, 20]*/
}

function: (Int) -> Boolean 作为参数,形式参数名 function 可随意修改, 形参类型 Int,
函数返回值 Boolean

  • 函数作为参数优化

1.省略小括号

如果函数只有一个参数,且这个参数类型是一个函数类型,则在调用函数时可以去掉函数名后面的小括号

省略前
list.pickNum({ x: Int -> x % 5 == 0})

省略后
list.pickNum{ x: Int -> x % 5 == 0}

2.将参数移动到小括号外面

如果一个函数有多个参数,但最后一个参数类型是函数类型,则在调用函数时可以将最后一个参数从括号中移除,并去掉参数之间的符号 ,

fun IntRange.pickNum(need: Int, function: (Int) -> Boolean) : List<Int>{
    val resultList = mutableListOf<Int>()
    for(i in this){                 
        if(function(i)){            
            resultList.add(i)      
        }
    }
    return resultList
}

省略前
list.pickNum(1, { x: Int -> x % 5 == 0})

省略后
list.pickNum(1) { x: Int -> x % 5 == 0}

3.使用 it 关键字

无论函数包含多少个参数,如果其中有参数是函数类型,并且函数类型满足只接收一个参数的要求,可用 it 关键字代替函数的形参及箭头

省略前
list.pickNum{ x: Int -> x % 5 == 0}

省略后
list.pickNum{ it % 5 == 0}

总结:

在 Kotlin 中,从函数定义形式方面来讲,函数可以有普通的定义方式,可以用表达式函数体,可以把 Lambda 赋值给变量,从函数放置的位置来讲,函数可以放置在类的外面(顶层函数)、可以放置在方法的内部(嵌套函数)、可以作为参数传递、可以作为函数的返回值。函数的功能非常强大与灵活,并且地位大大提升。

  • 函数作为返回值
enum class USER{//声明枚举
    NORMAL, VIP
}
fun getPrice(userType : USER) : (Double) -> Double{
    if (userType == USER.NORMAL){
        return {it}
    }
    return {price -> 0.88*price}
}

fun main(args: Array<String>) {
    val normalUserPrice = getPrice(USER.NORMAL)(200.0)
    println("普通用户价格: $normalUserPrice")
    val vipPrice = getPrice(USER.VIP)(200.0)
    println("超级会员价格: $vipPrice")
	/*
	普通用户价格: 200.0
	超级会员价格: 176.0
	*/
}

5.3 标准库中的高阶函数

  • 高阶函数操作集合

1.查找元素操作

来看下 Collections 中提供的一些用于查找、匹配集合中元素的方法

方法声明 功能描述
Iterable.find(predicate: (T) -> Boolean): T? 查找并返回指定条件的第一个元素,没有找到符合条件的元素返回 NULL
Iterable.first(predicate: (T) -> Boolean): T 查找并返回指定条件的第一个元素,如果没有找到抛出异常
List.last(predicate: (T) -> Boolean): T 查找并返回指定条件的最后一个元素,如果没有找到抛出异常
Iterable.single(predicate: (T) -> Boolean): T 查找并返回指定条件的元素,并且只能有一个元素,反之抛出异常
Iterable.takeWhile(predicate: (T) -> Boolean): List 查找并返回指定条件的列表,如果没有找到则返回一个空的列表,第一个元素必须满足条件,否则不会继续查找
Iterable.filter(predicate: (T) -> Boolean): List 查找并返回指定条件的列表,如果没有找到则返回一个空的列表,只要满足条件即可
Iterable.count(predicate: (T) -> Boolean): Int 查找(统计)出当前集合中满足指定条件的个数
fun main(args: Array<String>) {
    val list = listOf(-2, -1, 0, 1, 2)
    println("--------find--------")
    println("找出大于0的元素: ${list.find{ it > 0 }}")//找出大于0的元素: 1
    println("找出等于3的元素: ${list.find{ it == 3 }}")//找出等于3的元素: null
    println("--------first--------")
    println("大于0的元素: ${list.first{ it > 0 }}")//大于0的元素: 1
    println("大于0的元素: ${list.last{ it > 0 }}")//大于0的元素: 2
    //println("等于3的元素: ${list.first{ it == 3 }}")//Exception in thread "main" java.util.NoSuchElementException:
    // Collection contains no element matching the predicate.

    println("大于1的元素: ${list.single{ it > 1 }}")
    //println("大于-2的元素: ${list.single{ it > -2 }}")//Exception in thread "main" java.lang.IllegalArgumentException:
    // Collection contains more than one matching element.

    println("大于-3的元素: ${list.takeWhile{ it > -3 }}")//大于-3的元素: [-2, -1, 0, 1, 2]
    println("大于0的元素: ${list.takeWhile{ it > 0 }}")//大于0的元素: []  必须是第一个元素满足条件才能继续往下查找
    println("小于0的元素: ${list.takeWhile{ it < 0 }}")//小于0的元素: [-2, -1]

    println("大于-3的元素: ${list.filter{ it > -3 }}")//大于-3的元素: [-2, -1, 0, 1, 2]
    println("大于0的元素: ${list.filter{ it > 0 }}")//大于0的元素: [1, 2]
    println("小于0的元素: ${list.filter{ it < 0 }}")//小于0的元素: [-2, -1]

    val listSuper = listOf(60,80,100,120,140)
    println("查找大于100的元素个数: ${listSuper.count{ it > 100}}")//查找大于100的元素个数: 2
    println("查找小于60的元素个数: ${listSuper.count{ it < 60}}")//查找小于60的元素个数: 0
}

2.比较元素操作

来看下 Collections 中提供的一些用于比较集合中元素的方法

方法声明 功能描述
Iterable.maxBy(selector: (T) -> R): T? 查找并返回集合中的最大值
Iterable.minBy(selector: (T) -> R): T? 查找并返回集合中的最小值
Iterable.distinctBy(selector: (T) -> K): List 去除集合中重复的元素
fun main(args: Array<String>) {

    val list = listOf(-2,0,0,1,1,2)
    println("-----------查找最大值------------")
    println(list.maxBy { it })//2
    println("-----------查找最小值------------")
    println(list.minBy { it })//-2
    println("-----------集合去重-----------")
    println(list.distinctBy { it })//[-2, 0, 1, 2]
}

  • 标准库中的高阶函数

我们再来看下 Standard 类中的高阶函数

方法声明 功能描述 适用场景
repeat(times: Int, action: (Int) -> Unit) 用于重复执行 action() 函数 times 次,其中 times 表示重复执行的次数 适用于重复执行一个函数的场景
run(block: () -> R): R run()函数只接收一个 Lambda() 函数为参数,返回值为最后一行语句的值或 return 表达式的值 适用于 let() with()的任何场景
T.run(block: T.() -> R): R 调用指定的函数块,用 this 代表函数块中当前的引用对象,并且调用函数块的方法时,this 可省略。该函数的返回值是函数块中的最后一行语句的值或 return 表达式的值 适用于 let() with()的任何场景
T.let(block: (T) -> R): R 调用指定的函数块,该函数的返回值是函数块中的最后一行语句的值或 return 表达式的值 适用于处理变量不为 null 的场景或确定一个变量的作用域
T.apply(block: T.() -> Unit): T 调用指定的函数块,用 this 代表函数块中当前的引用对象,并且调用函数块的方法时,this 可省略。apply() 函数必须要有返回值,并且返回值是当前的引用对象 适用于对实例化对象中的属性进行赋值时,或动态创建一个 View,为该 View 绑定数据时
with(receiver: T, block: T.() -> R): R 将对象作为函数的参数,在函数内可以通过 this 指代该对象。返回值是函数块中的最后一行语句的值或 return 表达式的值 适用于调用同一个类的多个方法时,可以省去类名,直接调用类的方法即可
fun main(args: Array<String>) {
    println("-----------第1个参数为2时------------")
    repeat(2, { print("中国")})//中国中国
    println("-----------第1个参数为1时------------")
    repeat(1){ print("中国")}//中国
    println("-----------第1个参数为0时-----------")
    repeat(0){ print("中国")}

    val list = ArrayList<String>()
    list.run {
        this.add("土星")
        add("天王星")
        add("海王星")
    }
    println(list)//[土星, 天王星, 海王星]

    val list2 = ArrayList<String>()
    val value = list2.run {
        add("金星")
        println("集合数据: $list2")
        return@run size  //结束当前 run 函数,继续执行外部的语句
        add("火星")
        println("集合数据: $list2")
    }
    println("返回值是: $value")
    println("集合数据: $list2")
    /*集合数据: [金星]
    返回值是: 1
    集合数据: [金星]*/
}

5.4 内联函数

Kotlin 中提供 inline 修饰符,被 inline 修饰的 Lambda (函数) 称为内联函数,使用内联函数可以降低程序的内存消耗。

  • 使用内联函数
inline fun <T> check(lock : Lock, body: () -> T) : T{
    lock.lock()
    try {
        return body()
    }finally {
        lock.unlock()
    }
}

fun main(args: Array<String>) {
   val ll = ReentrantLock()
   check(ll, { print("这是内联函数方法体") })//ll 是一个 Lock对象
}

上面的代码在调用内联函数时,编译器会对 check(ll, { print(“这是内联函数方法体”) }) 进行优化,去掉方法名称以避免入栈出栈操作,直接执行方法体内容。

	lock.lock()
    try {
        return "这是内联函数方法体"
    }finally {
        lock.unlock()
    }
  • 禁用内联函数

在使用内联函数时,参数也会随之内联,这样会出现一个问题,如果参数有 Lambda 表达式时,Lambda 表达式便不是一个函数的对象,从而也不能作为参数来传递。在此,我们可使用 noinline 修饰符来修饰参数,禁止参数产生内联关系。

inline fun checkT(noinline function: (Int) -> Boolean){
    test(function)
}

fun test(function: (Int) -> Boolean){
    println("编译通过")
}

fun main(args: Array<String>) {
    checkT { x: Int -> x == 2 }//编译通过
}

后续

Kotlin 进阶之路(六) 泛型

你可能感兴趣的:(Kotlin,进阶之路,Kotlin,Lambda)