kotlin lambda简介

一、lambda表达式的格式

这里介绍该表达式的几个用法

data class Person(val name: String, val age: Int)

    val peoples = listOf(Person("aa", 29), Person("bb", 30))
    println(peoples.maxBy( { p: Person -> p.age }))
    // kotlin语法约定,如果lambda表达式是函数调用的最后一个实参,可以放到括号的外边
    println(peoples.maxBy() { p: Person -> p.age })
    // 当lambda是函数的唯一实参时,可以去掉空括号
    println(peoples.maxBy { p: Person -> p.age })
    // 如果lambda的参数能够推理出对应的类型,则可以把参数的类型去掉
    println(peoples.maxBy { p -> p.age })
    // 当lambda表达式只有一个的时候,可以不明确指定名称,默认会是it
    println(peoples.maxBy { it.age })
    // 但是要注意的是,如果是嵌套的lambda时候,最好显示地声明每个lambda的参数,避免搞混it到底是哪个值

如上是调用maxBy的几种方式,如下是maxBy需要传入的参数,是一个函数的类型:一个传入参数和一个返回参数

maxBy(selector: (T) -> R)

 

 

二、作为函数的代码块

java的接口

    public interface OnClickListener {
        void onClick(String v);
    }

 

1、使用java的方式进行创建匿名内部类的方式进行继承

        OnClickListener listener = new OnClickListener() {
            @Override
            public void onClick(String v) { // 这里实现OnClickListener的函数
                System.out.println("new OnClickListener():" + v);
            }
        };

        listener.onClick("kk"); //输出:new OnClickListener():kk

 

2、使用java的方式进行创建lambda

        OnClickListener listenerLambda = v -> {
            System.out.println("lambda:" + v);
        };
        listenerLambda.onClick("qqq"); // 输出:lambda:qqq

java在使用lambda在继承方面看似也不错,很简洁

 

kotlin的接口

interface OnClickListener1 {
    fun onClick(v: String)
}

fun testClick2(clickListener1: OnClickListener1) {
    clickListener1.onClick("kkqqq")
}

 

3、使用kotlin匿名内部类的方式

    testClick2(object: OnClickListener1 {
        override fun onClick(v: String) {
            println("object: OnClickListener1:${v}")
        }
    })
// 输出:object: OnClickListener1:kkqqq

看着跟java的匿名内部类差不多

 

4、使用kotlin的方式创建lambda

这边在使用kotlin的lambda调用上述的testClick2,按照正常的想法应该是能够正确推理出:OnClickListener1 接口

testClick2 { println("lambda interface:$it")} // 这样子调用老是提示:Type mismatch.

这是为啥呢?

这里先改变一下testClick2的参数为上述java的接口

fun testClick1(clickListener: OnClickListener) { // OnClickListener是上述的java的接口
    clickListener.onClick("kkqqq")
}

testClick1 { println("lambda interface:$it")} // 能正确输出:lambda interface:kkqqq

为啥使用java的接口可以但是kotlin的接口就不行呢?

官方的解释是 Kotlin 本身已经有了函数类型和高阶函数等支持,所以不需要再去转换。如果你想使用类似的需要用 lambda 做参数的操作,应该自己去定义需要指定函数类型的高阶函数。

 

三、SAM转换的概念

SAM转换的英文全拼是:Single Abstract Method Conversions。就是对于只有单个非默认抽象方法接口的转换 ,对于符合这个条件的接口在 Kotlin 中可以直接用 Lambda 来表示 (前提是 Lambda 的所表示函数类型能够跟接口中的方法相匹配)

例子:

上述的testClick1对应的接口是OnClickListener,对testClick1的传入参数是lambda表达式,该lambda表达式的类型是:(String) -> Unit , 正好跟OnClickListener接口的单一方法接口类型一样:void onClick(String v) ;所以testClick1的调用能够通过 :{ println("lambda interface:$it")} 替换OnClickListener作为testClick1的参数

 

如果SAM转换存在歧义该如何消除

1、这里有两个java接口

    public interface OnClickListener {
        void onClick(String v);
    }

    public interface OnClickListener2 {
        void onClick(String v);
    }

还有两个对应的kotlin的函数,参数分别是上述的OnClickListener和OnClickListener2

fun testClick1(clickListener: OnClickListener) {
    clickListener.onClick("kkqqq")
}
// 这里使用重载testClick1函数
fun testClick1(clickListener: OnClickListener2) {
    clickListener.onClick("kkqqq")
}

如果直接调用:testClick1 {println("lambda interface:$it")} 则会提示:Overload resolution ambiguity.

这里的正确调用:

方法一:使用lambda的方式:testClick1 (OnClickListener{println("lambda interface:$it")}) 该方法首先创建了一个`OnClickListener`的匿名实例,然后对其唯一的方法进行了实现。

方法二:使用匿名的内部类方式

    testClick1(object: OnClickListener {
        override fun onClick(v: String?) {
            println("lambda interface:$v")
        }
    })

方法三、testClick ({it: String -> println("lambda interface:$it")} as OnClickListener) 但是这个方法在运行的时候提示:cannot be cast to class OnClickListener。原因是因为:在Kotlin中,不能直接将Lambda表达式强制类型转换为Java SAM接口,即使该接口是一个函数接口

 

四、捕捉作用域中的变量

1、java中lambda要使用外部的局部变量需要是final才可行

2、kotlin中的lambda可以随便使用外部的局部变量,并且可以在lambda中修改该局部变量

fun updateLocalCount(): () -> Int {
    var count = 0
    val inc = {
        count+=1 // 在lambda内部将count加1
        count // 在这里返回改变count后的值
    }
    count += 1 // 在外部加1 count=1
    inc() // 这里调用这个lambda之后,count=2
    count += 1 // count=3
    println(count) // 这里输出的是3
    return inc
}

val inc = updateLocalCount() // 将updateLocalCount函数内部的lambda返回
println(inc()) // inc调用完之后count从3变成了4,从而这里输出的是:4

因为这里count是非final,它的值被inc捕获,则它的值被封装在一个特殊的包装器中,这样子inc内部就可以改变这个值

 

五、成员引用

如下是成员引用的格式,可以是成员变量也可以是成员函数

如下给出成员引用和全局函数的引用

data class Person(val name: String, val age: Int)

class User {
    fun printName(name: String) {
        println("username:${name}")
    }
}

    val p = Person("kk", 33)
    val personAgeFunction = Person::age //这里personAgeFunction的类型为: KProperty1<Person, Int> 即:入参是Person,返回是Int
    println(personAgeFunction(p)) //

    val dimAgeFunction = p::age // 这里dimAgeFunction的类型为:KProperty0<Int>
    println(dimAgeFunction())

    val user = User()
    val userNameFunc = User::printName // 如果本身有参数,那么在入参是User之后还要增加printName的传入参数String即: KFunction2<User, String, Unit>
    println(userNameFunc(user, "kk"))
    val dimNameFunc = user::printName // 同理这里只需要
    dimNameFunc("qqq")

    val gFunc = ::testGlobalFunc // 因为是全局的函数,所以这里没有成员引用的前缀。 这里的gFunc的类型为: KFunction0<Unit>
    gFunc()

如上有3种格式, 注意都有冒号

1、类名::类成员

2、类的实例::类成员

3、::全局函数

 

posted @ 2023-11-01 00:31  LCAC  阅读(10)  评论(0编辑  收藏  举报