美文网首页js css html
Kotlin-为什么要使用高阶函数?

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

作者: 大虾啊啊啊 | 来源:发表于2022-09-22 21:58 被阅读0次

    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, R> 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> 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<T> {
        @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<UserManager>() {
            override val creat: () -> UserManager = ::UserManager
        }
    }
    
    fun main() {
        println(UserManager.getInstance().hashCode())
        println(UserManager.getInstance().hashCode())
        println(UserManager.getInstance().hashCode())
    }
    

    5、剧终

    为什么要使用高阶函数?

    • 为了简洁、代码更少

    `

    相关文章

      网友评论

        本文标题:Kotlin-为什么要使用高阶函数?

        本文链接:https://www.haomeiwen.com/subject/gtdwortx.html