Kotlin 杂谈之 setOnClickListener 为什么可以简写成 Lambda
先上代码:
//例1
//java里定义的一个接口
public interface OnClickListener {
void onClick(View v);
}
//kotlin调用
view.setOnClickListener { //do something }
是不是这种代码在kotlin官方代码里很常见?
那么你有没有对这种写法有过疑问呢?
好,我们先把最原始的写法写出来
//例2
view.setOnClickListener (object: View.OnClickListener{
override fun onClick(v: View?) {
//do something
}
})
这是一个接口,我们使用object来创建一个OnClickListener的匿名类对象,创建对象就需要实现接口对应的方法,这部分和Java写法类似。
根据Kotlin种对匿名类的介绍:
Note: on the JVM, if the object is an instance of a functional Java
interface (i.e. a Java interface with a single abstract method), you
can create it using a lambda expression prefixed with the type of the
interface:
意思就是对于单个方法的java接口,可以简写成以接口类型作为前缀的Lambda表达式,所以我们再改下:
//例3
view.setOnClickListener ( OnClickListener{
//do something
})
好了,到这里你会发现还是没法像例1那样精简,还要多一个OnClickListener 和一对小括号,继续翻阅官方Lambda文档
Passing trailing lambdas
In Kotlin, there is a convention: if the last parameter of a function is a function, then a lambda expression passed as the corresponding argument can be placed outside the parentheses:
唯一涉及到简写可以去除小括号的地方就是这里,前提是函数最后一个参数是函数这个时候可以将函数放到括号外并省略括号,然而我们对比上面的函数并不符合这个前提,setOnClickListener 函数参数是一个OnClickListener的对象,并不是函数,当然这很好理解Java里的函数是不能作为其他函数参数传递的,但是Kotlin的高阶函数可以!那么我们是否可以设计这么一个Kotlin高阶函数来符合这一前提呢?当然可以:
//例4
//java里定义的一个接口
public interface OnClickListener {
void onClick(View v);
}
//java中的定义
public void setOnClickListener(OnClickListener l)
//Kotlin中可以这么定义
public fun setOnClickListener(l: ((v:View!)->Unit)!): Unit
//kotlin调用
view.setOnClickListener { //do something }
看到没Kotlin setOnClickListener 只有一个参数,这个参数是一个函数,已经符合Lambda的简写规则。如果setOnClickListener也是这么定义那么例1的简写是不是理所当然啦,但是实际setOnClickListener是由Java实现的不可能传递函数参数。
其实如果你在IDE里去写例3的代码的时候会有如下提示
意思就是这个代码属于SAM 构造可以使用Lambda替换,通过IDE精简后代码就和例1一致了。
好了现在问题转移到什么是SAM?
SAM,即 Single Abstract Method ,就是对于只有单个非默认抽象方法接口 ,对于符合这个条件的接口称之为 SAM,这是Java 8 里的一个概念,Kotlin也是支持的官方文档里有一段对SAM的介绍,这里做一个总结:
- Java 中的SAM在 Kotlin 中可以直接用 Lambda 来表示 ,当然前提是 Lambda 的所表示函数类型能够跟接口的中方法相匹配;
- 如果 Java 类有多个接受函数式接口的方法,可以指定特定的实现;
- 只适用于 Java 交互操作,因为 Kotlin 有高阶函数可以传递函数参数,所以不需要接口的实现;
- SAM 转换只适用于接口,而不适用于抽象类,即使这些抽象类也只有一个抽象方法。