敞开生长之旅!这是我参与「日新计划 12 月更文挑战」的第5天,点击检查活动概况


Kotlin中函数都是头等的,这意味着它能够存储在变量与数据结构中、作为参数传递给其他高阶函数以及从其他高阶函数回来。能够向操作任何其他非函数值一样操作函数。

为促成这点,作为一门静态类型编程语言的Kotlin运用一系列函数类型来表示函数并提供了一组专门的语言结构,例如Lambda表达式

这段话来自Kotlin文档。从没有触摸过Kotlin的话这段话的意思很难了解,上面有三个关键词【高阶函数】、【函数类型】、【Lambda表达式】先剖析这三个关键词然后再对上面这段话进行了解。

1.函数类型

函数类型便是函数的类型, 变量有类型IntString等,那函数的类型到底是指什么?

fun lastElement(str: String): Char {
    return str[str.length - 1]
}

上面的代码是之前用过的,意思是获取字符串的最后一个字符,其中参数类型是String,回来值类型是Char,将其抽出来便是【String -> Char】这就代表了函数的类型,一句话概括便是:将函数的【参数类型】和【回来值类型】抽象出来就得到了函数的【函数类型】。【(String) -> Char】的意思便是参数类型是【String】,回来值类型是【Char】的函数类型。这个比较好了解。相似的还有Kotlin中继承自BaseAdapter的几个办法

//函数类型:() -> Int
override fun getCount(): Int {
}
//函数类型:(Int) -> Any
override fun getItem(position: Int): Any {
}
//函数类型:(Int) -> Long
override fun getItemId(position: Int): Long {
}

2.高阶函数

了解饿了函数类型再来看下高阶函数。

高阶函数是将函数用作参数或回来值的函数, 这是高阶函数的界说。

  • 函数用作参数的高阶函数写法
fun main() {
    val result = answer(20) { 10 }
    println("result:$result")       //输出成果:result:30
}
/**
 * 高阶函数
 * 函数类型:(Int, add办法) -> Int
 */
fun answer(num: Int, add: () -> Int): Int {
    return num + add()
}

上面的代码用的是高阶函数中的函数用作参数的写法,界说一个answer办法,增加一个参数num和函数add,由于add办法回来值是一个Int类型因此能够跟num直接相加并回来成果,代码没有实际意义便是个比如

这儿可能会发生一个疑问:为什么result的调用方法是建立的?

将上面的代码转化成Java的写法就清楚了

public final class HighFunctionKt {
   public static final void main() {
      int result = answer(20, (Function0)null.INSTANCE);
      String var1 = "result:" + result;
      boolean var2 = false;
      System.out.println(var1);
   }
   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
   public static final int answer(int num, @NotNull Function0 add) {
      Intrinsics.checkNotNullParameter(add, "add");
      return num + ((Number)add.invoke()).intValue();
   }
}

能够看到add办法被转化成了Function0,而且num与add办法的回来值进行了相加,那么这儿有一个新的疑问invoke是什么?invokeFunction0的一个办法,效果便是调用函数,在Functions.kt类中,Function的数量到达Function21,从Function0Function21的差异便是传参数量的不同。

public interface Function0<out R> : Function<R> {
    /** Invokes the function. */
    public operator fun invoke(): R
}

再来看一个稍微杂乱的高阶函数比如,answer函数中又增加了一个参数,一起函数参数也支撑传参

fun main() {
    val add = answer(10, 20) { num1, num2 -> num1 + num2 }
    val minus = answer(10, 20) { num1, num2 -> num1 - num2 }
    println("add:$add")       //输出成果:result:30
    println("minus:$minus")       //输出成果:result:30
}
fun answer(num1: Int, num2: Int, result: (Int, Int) -> Int): Int {
    return result(num1, num2)
}

answer办法做了改动,传了两个参数,函数类型的参数也传入了两个参数,这样界说的效果是更灵敏

  • 函数用作回来值的高阶函数写法
fun main() {
    println(answer(10).invoke())	//输出成果:输入的数:10
}
fun answer(num: Int): () -> String {
    return { "输入的数:${num}" }
}

这儿的invoke便是一个调用函数的功能。编译成Java代码如下所示:

public final class HighFunctionKt {
    public static final void main() {
        Object var0 = answer(10).invoke();
        boolean var1 = false;
        System.out.println(var0);
    }
    // $FF: synthetic method
    public static void main(String[] var0) {
        main();
    }
    @NotNull
    public static final Function0 answer(final int num) {
        return (Function0)(new Function0() {
            // $FF: synthetic method
            // $FF: bridge method
            public Object invoke() {
                return this.invoke();
            }
            @NotNull
            public final String invoke() {
                return "输入的数:" + num;
            }
        });
    }
}
  • 运用场景举例

Android开发过程中RecycleView是常用的一个组件,可是它自身不支撑点击事情,现在,假定我们在Adapter中有两个点击事情,增加和删去,常用的写法会先界说一个接口,对该接口界说一个变量,然后界说一个办法,代码如下:

private lateinit var mOnItemAddClickListener: OnItemAddClickListener
private lateinit var mOnItemDeleteClickListener: OnItemDeleteClickListener
interface OnItemAddClickListener {
    fun onItemAddClick(position: Int)
}
interface OnItemDeleteClickListener {
    fun onItemDeleteClick(position: Int)
}
fun setOnItemAddClickListener(onItemAddClickListener: OnItemAddClickListener) {
    mOnItemAddClickListener = onItemAddClickListener
}
fun setOnItemDeleteClickListener(onItemDeleteClickListener: OnItemDeleteClickListener) {
    mOnItemDeleteClickListener = onItemDeleteClickListener
}
holder.ivAdd.setOnClickListener {
    mOnItemAddClickListener.onItemAddClick(position)
}
holder.ivDelete.setOnClickListener {
    mOnItemDeleteClickListener.onItemDeleteClick(position)
}
adapter.setOnItemAddClickListener(object :DemoAdapter.OnItemAddClickListener{
    override fun onItemAddClick(position: Int) {
        TODO("Not yet implemented")
    }
})
adapter.setOnItemDeleteClickListener(object :DemoAdapter.OnItemDeleteClickListener{
    override fun onItemDeleteClick(position: Int) {
        TODO("Not yet implemented")
    }
})

用高阶函数对其进行优化后的代码如下:

private lateinit var mOnItemAddClickListener: (Int) -> Unit
private lateinit var mOnItemDeleteClickListener: (Int) -> Unit
fun setOnItemAddClickListener(listener: (Int) -> Unit) {
    mOnItemAddClickListener = listener
}
fun setOnItemDeleteClickListener(listener: (Int) -> Unit) {
    mOnItemDeleteClickListener = listener
}
holder.ivAdd.setOnClickListener {
    mOnItemAddClickListener.invoke(position)
}
holder.ivDelete.setOnClickListener {
    mOnItemDeleteClickListener.invoke(position)
}
adapter.setOnItemAddClickListener {
}
adapter.setOnItemDeleteClickListener {
}

这两种写法的代码进行对比能够发现高阶函数的实现方法中没有界说接口,一起它代码量显著减少,代码也变得愈加简练。

3.体系规范高阶函数

体系的规范高阶函数来自Standard.kt,里边的办法也是比较常用的

  • run
public inline fun <R> run(block: () -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

block是函数式参数的称号,传入的函数类型是()-> RR是泛型

这段代码的意思便是调用传入的函数并回来成果,return block()便是传入函数的调用。

怎样用?or有什么用?

fun main() {
    run { println(add(1, 2)) }
}
fun add(num1: Int, num2: Int): Int {
    return num1 + num2
}

效果便是构建Lambda更便利

  • T.run
public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

这个run更上面那个的差异在于它有一个接收者,有了这个接收者就能够运用上下文了,例如:

val num = 10
num.run { println("result:${this + 1}") }	//输出成果:result:11

还有一种状况,例如要从某个目标中取出它的一些特点的值也能够经过T.run,一起由于能够将this省掉因此代码就能够这么写:

class Person(val name:String, var age:Int)
val person = Person("张三", 19)
person.run{
    println("name:$name")
    println("age:$age")
}

再举个比如,TextView运用T.run修改特点并赋值

holder.tvText.run {
    text = "自界说文本"
    setTextColor(context.getColor(R.color.color_000000))
    textSize = 20F
}
  • whith
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

with的运用方法与T.run相似,with在回来值上带了一个接收者,看下面代码

with(holder.tvText) {
    text = "自界说文本"
    setTextColor(context.getColor(R.color.color_000000))
    textSize = 20F
}
  • T.apply
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

这个函数的意思便是接收者传入了什么回来值便是什么,运用案例:

val person = Person("张三", 19)
person.apply {
    println("name:$name")			//输出成果:张三
    println("age:$age")				//输出成果:19
}.age = 10					   		//把张三的年龄修改我10岁
println("name:${person.name}")		//输出成果:张三
println("age:${person.age}")		//输出成果:10

有什么用?

Android中会遇到根据状况修改Button样式然后还要响应点击事情的状况,常用写法就不在这儿讲了,这儿用T.apply实现:

button.apply {
    text = "提交"
    setTextColor(context.getColor(R.color.color_000000))
    background = context.getDrawable(R.drawable.shape_solid_4dp_4e6cf5)
}.setOnClickListener { 
    //点击事情
}

这行代码是不是很简练。

还有webview的运用

webView.apply {
    settings.javaScriptEnabled = true
    settings.useWideViewPort = true
}.loadUrl("https://www.baidu.com/")

T.apply很灵敏,具体问题具体剖析就好。

  • T.also
public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

T.also的意思便是用传参调用指定函数并回来这个参数

button.also {
    it.text
}

这儿的button在调用also之后在它的里边只能用it也必须用it,这个it指的是button自身,而T.apply中是this指的是Button自身,而且这个this是能够被省掉的。运用过程中的差异不大。

  • T.let
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

T.let的意思便是运用传参调用指定函数然后回来成果,代码如下

val person = Person("张三", 19)
val str = "Hello Kotlin"
val result = person.let {
    it.name == "张三"
}
println("result:$result")		//输出成果:result:true
println("length:${str.let { it.length }}")	//输出成果:length:12

在Person类中有一个name = 张三的实例,经过let判断后回来true;获取str字符串的长度得到终究成果12。

  • T.takeIf
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
    contract {
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (predicate(this)) this else null
}

T.takeIf的意思便是假如符合条件则回来传入的值,不然回来null

val person = Person("张三", 19)
val result = person.takeIf {
    it.name == "李四"
}
println("result:${result?.name}")		//输出成果:result:null
val result = person.takeIf {
    it.name == "张三"
}										//条件建立回来person目标
  • T.takeUnless
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? {
    contract {
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (!predicate(this)) this else null
}

T.takeUnlessT.takeIf正好相反,假如满意条件则回来null,不满意则回来正确的谓词

val person = Person("张三", 19)
val result = person.takeUnless {
    it.name == "李四"
}
println("result:${result?.name}")		//输出成果:result:张三
val result = person.takeUnless {
    it.name == "张三"
}
println("result:${result?.name}")		//输出成果:result:null
  • repeat
public inline fun repeat(times: Int, action: (Int) -> Unit) {
    contract { callsInPlace(action) }
    for (index in 0 until times) {
        action(index)
    }
}

action函数便是一个重复履行的函数,从0开始

repeat(2) {
    println("履行第:${it}次")
}
//输出成果:
//履行第:0次
//履行第:1次

4.Lambda表达式

Lambda表达式在Java中现已用的比较多了,经过它能够简化代码和提高开发功率,所以我们能够把Lambda表达式了解为函数的简写

例如view的点击事情在Kotlin中调用时便是这样的:

fun setOnClickListener(l: ((View!) -> Unit)?){
}
//调用时能够这么写:
button.setOnClickListener{
}

开头提出了一个结论:在Kotlin中函数是头等的,为什么这么说呢?

  • Kotlin的函数能够独立于类之外,这便是顶层函数
  • Kotlin的函数能够作为参数也能够作为函数,它被称为高阶函数和Lambda
  • Kotlin的函数能够向变量一样,这叫做函数引证

【Kotlin回顾】5.高阶函数与Lambda