匿名函数

我们在定义函数时,不给他名字,这个函数就是匿名函数了。匿名函数通常用法是作为参数传递给其他函数或者被其他函数返回。

Kotlin因有了匿名函数,我们开发者就可以轻松实现对kotlin标准库内置函数的定制。案例:

fun main() {
    val a = "luffy".count()
    var b = "luffy love honey".count({ letter -> letter.equals('y') })
    println("a == " + a)
    println("b == " + b)
}

匿名函数也是有类型,类型由传入的参数和返回值决定。匿名函数可以当作变量赋值给函数类型变量,就像其他变量一样,匿名函数也可以在代码中传递。和具名函数一样,匿名函数可以是无参数或接受一个或多个不同类型的参数。

fun main() {
    //先定义匿名函数变量,noNameval是变量名,() -> String 表示该变量是无参数且返回值类型为String的匿名函数类型。
    val noNameval: () -> String
    //再赋值
    noNameval = {
        val holiday = "Mid-Autumn Festival"
        "Happy $holiday"
    }

    //直接赋值
    val noNameval1: () -> String = {
        val holiday = "Mid-Autumn Festival"
        "Happy $holiday"
    }
    println(noNameval())
    println(noNameval1())
}

有小伙伴可能注意到了,上面案例中,匿名函数返回值类型是String,但是我并没有用return返回字符串,其实原因是匿名函数一般情况下会隐式返回最后一个语句的执行结果,而不需要我们哟用return关键字显式返回数据。

带参数的匿名函数

参数的类型放在匿名函数的类型定义中,参数名则放在函数定义中:

    //带一个参数的匿名函数
    val noNameval2: (String) -> String = { name ->
        val holiday = "Mid-Autumn Festival"
        "$name,Happy $holiday"
    }

当匿名函数只要一个参数时,可以使用it关键字表示参数名:

    //用it关键字改造
    val noNameval3: (String) -> String = {
        val holiday = "Mid-Autumn Festival"
        "$it,Happy $holiday"
    }

类型推断

定义一个变量时,如果把匿名函数作为变量赋值给它,不需要显式指明变量类型,kotlin支持类型推断。

    val noNameval4 = {
        val holiday = "Mid-Autumn Festival"
        "Happy $holiday"
    }

类型推断也支持带参数的匿名函数,但为了帮助编译器更准确地推断变量类型,必须标明匿名函数的参数名和参数类型。

    val noNameVal5 = { name: String, year: Int ->
        val holiday = "Mid-Autumn Festival"
        "$name,Happy $holiday,$year"
    }

我们将匿名函数称为lambda,将它的定义称为lambda表达式,它返回的数据称为lambda结果。为什么叫lambda?lambda也可以用希腊字符 λ 表示,是lambda演算的简称,lambda是一套数理演算逻辑,是由数学家Alonzo Church(阿隆佐.丘齐)于20世纪30年代发明,在定义匿名函数时,使用了lambda演算记法。

定义参数是函数的函数

即函数的参数是另外一个函数。

fun main() {
    val discountWords = { goodsName: String, hours: Int ->
        val currentYear = 2022
        //给变量加上大括号{}之后,字符串就没有空格了
        "${currentYear}年,${goodsName}双11大促销倒计时还有:$hours 小时"
    }

    getDiscountWords("牙膏", discountWords)
}

fun getDiscountWords(goodsName: String, discountWords: (String, Int) -> String) {
    //返回一个1到24的随机数
    val hour = (1..24).shuffled().last()
    println(discountWords(goodsName, hour))
}

简略写法

如果一个函数的lambda排在最后,或者是唯一的参数,那么lambda外面的圆括号可以省略掉。

fun main() {
    //如果一个函数的lambda排在最后,或者是唯一的参数,那么lambda外面的圆括号可以省略掉
    val a = "luffy".count { it == 'f' }
    println("a = $a")
    getDiscountWords("牙膏") { goodsName: String, hours: Int ->
        val currentYear = 2022
        "${currentYear}年,${goodsName}双11大促销倒计时还有:$hours 小时"
    }
}

//具名函数
private fun getDiscountWords(goodsName: String, discountWords: (String, Int) -> String) {
    //返回1到24的随机数
    val hour = (1..24).shuffled().last()
    println(discountWords(goodsName, hour))
}
函数内联

lambda可以让你更灵活地编写应用,但是灵活也是要付出代价的。在JVM上,我们定义的lambda会以对象实例的形式存在,JVM会为所有同lambda打交道的变量分配内存,这就产生了内存开销,更糟的是,lambda的内存开销会带来严重的性能问题。幸运的是,kotlin有一种优化机制叫内联。有了内联,JVM就不需要使用lambda对象实例了,因而避免了变量内存分配。哪里需要使用lambda,编译器就会将函数体赋值粘贴到哪里,跟C语言中的宏定义比较类似,内联关键字是inline

使用lambda的递归函数无法内联,因为会导致复制粘贴无限循环,编译器也会警告提示。

 我们show kotlin bytecode,再把它反编译一下,就能看到lambda确实是以实例的形式存在:
Kotlin学习笔记(二)_匿名内部类
Kotlin学习笔记(二)_kotlin_02

 使用内联之后,可以看到在有lambda的地方,JVM会帮我们复制粘贴lambda:

Kotlin学习笔记(二)_java_03

Kotlin学习笔记(二)_赋值_04

 函数引用

要把函数作为参数传递给其他函数使用,除了使用 lambda,kotlin还提供了函数引用的方式,函数引用可以把一个具名函数转换成一个值参,使用lambda的地方都可以使用函数引用。

fun main() {
    showOnBoader("牙膏", ::getDiscountWords)
}

private fun showOnBoader(goodsName: String, discoutWords: (String, Int) -> String) {
    val hours = (1..24).shuffled().last()
    println(discoutWords(goodsName, hours))
}

private fun getDiscountWords(goodsName: String, hours: Int): String {
    val currentYear = 2020
    return "${currentYear}年双十一${goodsName}大促销倒计时:$hours 小时"
}
 函数类型作为返回类型

把函数类型作为一个函数的返回类型:

fun main() {
    val discountWords = configDiscountWords()("洗衣液")
    println(discountWords)

    val discountWords1 = configDiscountWords()
    println(discountWords1("洗衣液"))
}

fun configDiscountWords(): (String) -> String {
    val currentYear = 2020
    val hours = (1..24).shuffled().last()
    return { "${currentYear}年双十一${it}大促销倒计时:$hours 小时" }
}
闭包

在Kotlin中 ,匿名函数能修改并引用定义在自己的作用域之外的变量,匿名函数引用着定义自身的函数里的变量,可以这么理解,匿名函数就好比胎儿,定义匿名函数的函数就好比母体,母体的营养物质可供胎儿吸收使用。Koltlin中的lambda就是匿名函数,就是闭包,它们三个是一个东西。能接收函数或者返回函数的函数又叫高级函数,高级函数广泛应用于函数式编程中。

fun configDiscountWords(): (String) -> String {
    var currentYear = 2020
    val hours = (1..24).shuffled().last()
    return {
        //在kotlin中,匿名函数能够修改并引用定义在自身作用域之外的变量,即匿名函数可以引用定义自身的函数里面的变量
        currentYear += 10
        "${currentYear}年双十一${it}大促销倒计时:$hours 小时"
    }
}
lambda与匿名内部类

为什么要在代码中使用函数类型?原因就是函数类型可以让开发者少写模式化代码,写出更灵活的代码。Java8支持面向对象编程和lambda表达式,但不支持将函数作为参数传给另一个函数或变量,不过Java的替代方案是匿名内部类。

import java.security.SecureRandom;

/**
 * @author luffy
 * @description Java中的匿名内部类
 */
public class JavaAnonymousClass {

    public static void main(String[] args) {
        showOnBoard("农夫山泉", (goodsName, hour) -> {
            int currentYear = 2022;
            return String.format("%d年,双11%s促销倒计时:%d 小时", currentYear, goodsName, hour);
        });
    }

    public interface DiscountWords {
        String getDiscountWords(String goodsName, int hour);
    }

    public static void showOnBoard(String goodsName, DiscountWords discountWords) {
        int hour = new SecureRandom().nextInt(24);
        System.out.println(discountWords.getDiscountWords(goodsName, hour));
    }

}

今天内容虽然不多,但对于初学Kotlin的同学还是有难度的,想一起学习交流的,私信我进粉丝群,也欢迎大家评论区留言讨论~