1、为什么要使用高阶函数?
先来看看两段代码,在Andriod自定义View中的一个小例子,分别用Java和Kotlin来实现
Java
public class DemoView {
interface OnClickListener {
void onClick();
}
interface OnItemClickListener {
void onItemClick(int position);
}
private OnClickListener onClickListener;
private OnItemClickListener onItemClickListener;
public void setOnClickListener(OnClickListener onClickListener) {
this.onClickListener = onClickListener;
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
public void test(){
onClickListener.onClick();
onItemClickListener.onItemClick(100);
}
public static void main(String[] args) {
DemoView demoView = new DemoView();
demoView.setOnClickListener(new OnClickListener() {
@Override
public void onClick() {
System.out.println("onClickListener");
}
});
demoView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(int position) {
System.out.println("onItemClickListener:"+position);
}
});
demoView.test();
}
}
Kotlin
class TestView {
//用函数类型替代接口
private var onClickListener: (() -> Unit)? = null
private var onItemClickListener: ((Int) -> Unit)? = null
fun test() {
onClickListener?.invoke()
onItemClickListener?.invoke(100)
}
/**
* 用lambda表达式作为函数参数
*/
fun setOnClickListener(onClickListener: () -> Unit) {
this.onClickListener = onClickListener
}
fun setOnItemClickListener(onItemClickListener: (Int) -> Unit) {
this.onItemClickListener = onItemClickListener
}
}
fun main() {
val testView = TestView()
testView.setOnClickListener {
println("onClickListener")
}
testView.setOnItemClickListener {
println("onItemClickListener${it}")
}
testView.test()
}
最终实现的效果一样
onClick
onItemClickListener:100
可以看到Kotlin的代码比Java少很多,Kotlin的设计者怎么实现的呢?实际分为两部分:
- 用函数类型替代接口
/**
* 用lambda表达式作为函数参数
*/
fun setOnClickListener(onClickListener: () -> Unit) {
this.onClickListener = onClickListener
}
- 用Lambda表达式作为函数入参
//用函数类型替代接口
private var onClickListener: (() -> Unit)? = null
以上我们可以小结:
Kotlin引入高阶函数,省了两个接口的定义,对于调用者来说,代码更加简洁
2、高阶函数中一些名称的含义
什么是函数类型?
函数类型,顾名思义是函数的类型,我们知道一个变量有类型,那么函数也有类型。例如以下函数:
fun add(a: Int, b: Int): Int {
return a + b
}
它的类型是(Int,Int)->Int,也就是一个函数的类型包含了函数的入参和返回类型结合在一起,就是函数的类型。
那么我们就可以类似定义变量的方式定义一个函数
//函数名称 函数类型
var function: ((Int, Int) -> Int)? = null
当然我们也可以直接对函数初始化,并执行函数。
fun main() {
//函数名称 函数类型
var function: ((Int, Int) -> Int)? = { a, b ->
a + b
}
val result = function?.invoke(1, 2)
println(result)
}
什么是函数的引用
我们定义一个add函数
fun add(a: Int, b: Int): Int {
return a + b
}
将我们的add函数通过引用的方式,赋给我们定义的函数类型,其中::add就是函数的引用
fun main() {
//函数名称 函数类型 函数的引用
var function: ((Int, Int) -> Int) = ::add
}
fun add(a: Int, b: Int): Int {
return a + b
}
什么是高阶函数?
- 函数的参数中包含了函数的类型
- 函数的返回值是函数的类型
满足以上某一个条件的函数,称之为高阶函数,如以下的例子:
fun main() {
add(1, 2) {
println(it)
}
println(get().invoke(2, 1))
}
/**
* 函数中的参数包含了函数类型
*/
fun add(a: Int, b: Int, f: (Int) -> Unit) {
f.invoke(a + b)
}
fun del(a: Int, b: Int): Int {
return a - b
}
/**
* 返回值是函数类型
*/
fun get(): ((Int, Int) -> Int) {
return ::del
}
什么是Lambda表达式
Lambda表达式我们可以理解位函数的简写,分为两种用途
- 使用Lambda表达式声明创建一个函数
- 使用Lambda表达式作为函数类型的入参
如下面的例子:
fun main() {
//1、使用lambda表达声明创建一个函数
val add: (Int, Int) -> Int = { a, b ->
a + b
}
val result = add.invoke(1, 2)
println(result)
//2、使用lambda表示作为函数的入参
val result2 = add { a, b ->
a + b
}
println(result2)
}
/**
*使用lambda表示作为函数的入参
*/
fun add(f: (Int, Int) -> Int): Int {
return f.invoke(3, 2)
}
什么是SAM转换?
SAM表示是Single Abstract Method (简单的抽象方法的类或者接口)但是在Kotlin和Java8里,SAM只代表只有一个抽象方法的接口。因此只要满足接口中只有一个方法,我们就可以使用SAM转换,也就是我们可以使用Lambda表达式来简写接口类的参数。如:
转换前
interface OnClickListener {
void onClick();
}
public void setOnClickListener(OnClickListener onClickListener) {
this.onClickListener = onClickListener;
}
demoView.setOnClickListener(new OnClickListener() {
@Override
public void onClick() {
System.out.println("onClickListener");
}
});
转换后
/**
* 用lambda表达式作为函数参数
*/
fun setOnClickListener(onClickListener: () -> Unit) {
this.onClickListener = onClickListener
}
//使用SAM转换
TestView().setOnClickListener {
}
我们声明一个函数类型变量,并通过函数的引用赋值给此变量
fun main() {
//函数的引用add赋值给 addFun函数变量
val addFun: (Int, Int) -> Int = ::add
println(addFun.invoke(1, 2))
}
/**
* 普通的函数
*/
fun add(a: Int, b: Int): Int {
return a + b
}
这样一来我们发现比较麻烦,遍可以通过SAM转换的方式创建此函数
fun main() {
//使用SAM转换
val addFun: (Int, Int) -> Int = {
a,b->
a+b
}
println(addFun.invoke(1, 2))
}
我小结就是
当我们的接口中只有一个实现函数的时候,我们可以通过Kotlin中的函数类型替代。而在Kotlin中我们又可以通过Lambda表达式来简写声明一个函数,因此我们就可以通过此方式替代接口。
因此对于两种情况都是可以使用SAM转换:
(1)接口中只有一个实现函数
(2)声明创建一个函数类型的实现
在Kotlin中我们引入了函数的类型,也就是从此之后不仅仅一个普通的变量有类型,函数我们也可以当成一个变量,也拥有类型,称之为函数的类型。
这样一来函数就可以拥有了普通变量等同的功能,函数类型变量的声明,创建赋值。函数类型的传参,函数类中的返回值。函数类型变量的使用。
- 将函数的参数类型和返回值类中抽出来,就代表了这个函数的类型
- 如果一个函数的参数或者返回值的类型是一个函数类型,那这个函数就是高阶函数
- Lambda表达式是函数一种简写
3、分析高阶函数在Kotlin源码中下实现
let
fun main() {
var a = "a"
a.let {
val result = "$it bcd"
println(result)
}
}
源码分析
public inline fun T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
- let函数是对泛型T扩展的一个函数,因此所有类型的变量都可以调用此函数。
- let函数的入参block是一个函数函数类型,因此let是一个高阶函数,block的入参是T,就是被扩展的对象,返回值是R。并且整个let函数的返回值由block函数的返回值决定。
apply
fun main() {
var a = "a"
a.apply {
println(this)
}
}
源码分析
@kotlin.internal.InlineOnly
public inline fun T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
apply和let一样都是对T进行扩展,并且入参都是函数类型,因此都是高阶函数。不同的地方在于apply的入参括号旁边多了一个T.
block: T.() -> Unit
这种函数类型称为带接收者的函数类型,并且接收者是T,也就是被扩展的对象。因此在block函数中我们便可以使用该接收者T。因此在以上的代码中,我们直接可以通过this使用接收者。
var a = "a"
a.apply {
println(this)
}
4、使用高阶函数改版抽象模板的单例
abstract class BaseSingleInstance {
@Volatile
private var instance: T? = null
protected abstract val creat: () -> T
fun getInstance(): T {
return instance ?: synchronized(this) {
instance ?: creat.invoke().also { instance = it }
}
}
}
class UserManager private constructor() {
companion object : BaseSingleInstance() {
override val creat: () -> UserManager = ::UserManager
}
}
fun main() {
println(UserManager.getInstance().hashCode())
println(UserManager.getInstance().hashCode())
println(UserManager.getInstance().hashCode())
}
5、剧终
为什么要使用高阶函数?
- 为了简洁、代码更少
`