Kotlin-为什么要使用高阶函数?

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、剧终

为什么要使用高阶函数?

  • 为了简洁、代码更少

`

你可能感兴趣的:(Kotlin-为什么要使用高阶函数?)