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

为什么要使用高阶函数?

  • 为了简洁、代码更少

`

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • 介绍 Kotlin是函数式编程,所以可以把函数作为参数传递给函数,或者作为函数的返回值使用,我们称其为高阶函数。本...
    烧伤的火柴阅读 849评论 0 0
  • 定义:一个用函数作为参数或者返回值的函数如何定义:()->Unit 括号里面代表函数的参数,箭头后面代表函数的返回...
    JuliusL阅读 785评论 0 1
  • 用过Kotlin的同学都知道,那些扩展方法用起来简直不要太爽,那么这些扩展方法是怎么定义实现的呢,本文介绍了Kot...
    我是黄教主啊阅读 11,905评论 0 22
  • 高阶函数的作用 就是用来定义函数式编程里面接收Lambda表达式的函数。 高阶函数的定义 如果一个函数接收另一个函...
    在下陈小村阅读 243评论 0 0
  • 高阶函数 高阶函数可以将函数作为参数或者是返回值 forEach提供遍历集合的功能,forEach其实是IntAr...
    Guow110阅读 408评论 0 0