Kotlin基础九

481 阅读9分钟

内容 

  • 什么高阶函数
  • 函数类型怎么表示
  • 声明和调用函数做为参数的高阶函数
  • 模仿一个filter函数
  • java中怎么去调用kotlin中的高阶函数
  • 函数类型的参数默认值和null
  • 声明和调用函数做为返回值的高阶函数
  • 内联函数
  • 控制逻辑关键字

一高阶函数

1.1什么是高阶函数

在kotlin中函数可以用lambda或者函数引用来表示。高阶函数就是以另外一个函数作为参数或者返回值的函数就是高阶函数。因此,任何以lambda或者函数引用作为参数的函数,或者返回值为lambda或函数引用的函数,或者两者都满足的函数都是高阶函数。例如:lazy(),maxBy(),forEach(),filter(),map(),all(),any(),count(),find(),filterMap(),with(),这些函数都是接收lambda做为参数的函数,它们都是高阶函数。

1.2函数类型怎么表示

入门我们需要用函数做为参数,遇到的第一个问题就是怎么声明参数的类型?

我们之前都已经见过这样的代码:

val sum = { x:Int, y:Int -> x + y }
val action= { Log.e("rrrrrrrrrr", "rrrrrrrr") }

这里sum和action都是函数,但是我们已经省略了函数的类型,那么我们加上他的类型代码变成这个样子:

val sum: (Int, Int) -> Int = { x, y -> x + y }
val action: () -> Unit = { Log.e("rrrrrrrrrr", "rrrrrrrr") }

看到这里我们似乎知道函数类型怎么表示了:

(Int,String)->Int  :箭头左边表示当前函数的参数类型,右边表示函数的返回值类型,他们整体就是函数类型的写法。

注意

  • 如果返回值是Unit类型,这里不能省略
  • 上边代码还可以显示的声明函数里边的形参,类似这样的写:
val sum: ( x :Int,y: Int) -> Int = { x, y -> x + y }
  • 如果返回值可以为null应该这样写
val sum: ( x :Int,y: Int) -> Int? = {x,y ->null}
  • 如果函数可以为null,我们必须把类型包裹起来
val sum: ((x: Int, y: Int) -> Int)? = null

1.3声明和调用函数做为参数的高阶函数

我们已经知道了函数类型是怎么声明的,现在我们去定义一个简单的函数做为参数的高阶函数:

fun TwoAndThere(luoji: (x: Int, y: Int) -> Int) {
    Log.e("rrrrrrrr", "${luoji(2, 3)}")
}

这里定义了一个高阶函数,参数是另外一个函数,在这个高阶函数中值是高阶函数内部提的对应的运算逻辑却是外部传递过来的。这样很好的让使用者根据自己的逻辑去得到返回值,对于拿到值的处理部分完全一样。优点在于:抽离不同逻辑仍出去,相通逻辑封装成高阶函数。

我们可以传递这两个数任意的运算逻辑:

TwoAndThere { x, y -> x + y }
TwoAndThere { x, y -> x * y }

这里分别调用后传递了他们的加法和乘法逻辑,只要你想,你可以传递任意的运算操作。这里才真正呈现了局部函数的优点。

1.4模仿一个filter函数

我们知道集合操作的fitler操作符,当然我们这里实现的是字符串的fitler,因为它更加简单容易理解,但是基本原理是一样的,并且没有惰性操作。

我们现在想过滤掉字符串中所有非字母的字符,我们可以去扩展String类,代码类似这样:

fun String.filter(luoji: (Char) -> Boolean): String {

    var buffer = StringBuffer()
    for (index in 0..length - 1) {
        var cha = get(index)
        if (luoji(cha)) buffer.append(cha)
    }
    return buffer.toString()
}

然后我们就可以调用我们自己定义的操作符,来实现过滤字符串,并返回一个新的字符串:

var oldstr = "1234abc"
var newstr = oldstr.filter { it -> it in 'a'..'z' }
Log.e("rrrrrrr", newstr)

这里就过滤掉了所有非字母的字符串。

1.5java中怎么去调用Kotlin的高阶函数

我们知道java中没有局部函数的概念和语法的支持,那么我们在kotlin中声明的高阶函数怎么在java中调用呢?其实,函数类型被声明为普通的接口:一个函数类型的变量就是FunctionN接口的一个实现,Kotlin标准库定义了一系列的接口,这些接口对应于不同参数数量的函数:FunctionO<R>(没有参数的函数)、Function1<P1,R>(一个参数的函数),等等。每个接口定义了一个invoke方法,调用这个方法就会执行函数。一个函数类型的变量就是实现了对应的FunctionN接口的实现类的实例,实现类的invoke方法包含了lambda函数体。

像这样:

StringUtils.ExpendKt.filter("123dsd", new Function1<Character, Boolean>() {
    @Override
    public Boolean invoke(Character character) {
//这里去添加你要添加的逻辑
        return false;
    }
});

或者这样:

class Utils {

    fun aaa(luoji: (Int) -> Int): Int {
        return luoji(42)
    }


}
Utils utils = new Utils();
int aaa = utils.aaa(new Function1<Integer, Integer>() {
    @Override
    public Integer invoke(Integer integer) {
        return integer + 1;
    }
});

1.6函数类型的参数默认值和null值

我们去定义一个把集合打印成字符串的集合扩展函数:

fun <T> Collection<T>.jsonToString(separator: String = ","
                                   , prefix: String = "["
                                   , postfix: String = "]"
                                 ): String {
    val result = StringBuilder(prefix)
    for ((index, element) in this.withIndex()) {
        if (index > 0) result.append(separator)
        result.append(element.toString())
    }
    result.append(postfix)
    return result.toString()

}

这个扩展函数里边都写了默认的参数,但是你会发现这个扩展方法不够灵活,假如你集合里边全部是有大写有小写的字符串,你要求打印出来的全部是小写字母。很明显这个扩展函数满足不了你的需求:这里我们可以使用我们的扩展函数去完成这个需求。

fun <T> Collection<T>.jsonToString(separator: String = ","
                                   , prefix: String = "["
                                   , postfix: String = "]"
                                   , transfrom: (T) -> String = { it.toString() }): String {
    val result = StringBuilder(prefix)
    for ((index, element) in this.withIndex()) {
        if (index > 0) result.append(separator)
        result.append(transfrom(element))
    }
    result.append(postfix)
    return result.toString()

}

这样就把关键逻辑扔到了外边,你可以随心定义,或者就用默认的实现。

另一种选择是声明一个参数为可空的函数类型,然后下边去判断调用或者安全调用。代码如下:

fun <T> Collection<T>.jsonToString(separator: String = ","
                                   , prefix: String = "["
                                   , postfix: String = "]"
                                   , transfrom: ((T) -> String)? = null): String {
    val result = StringBuilder(prefix)
    for ((index, element) in this.withIndex()) {
        if (index > 0) result.append(separator)
        if (transfrom!=null)
        result.append(transfrom(element))
    }
    result.append(postfix)
    return result.toString()

}

亦或是利用函数类型是一个包含invoke方法的接口的具体实现。作为一个普通方法,invoke可以通过安全调用。

fun <T> Collection<T>.jsonToString(separator: String = ","
                                   , prefix: String = "["
                                   , postfix: String = "]"
                                   , transfrom: ((T) -> String)? = null): String {
    val result = StringBuilder(prefix)
    for ((index, element) in this.withIndex()) {
        if (index > 0) result.append(separator)
      
        result.append(transfrom?.invoke(element)?:element.toString())
    }
    result.append(postfix)
    return result.toString()

}

1.7函数类型做为返回值

enum class Delivery { STANDARD,EXPEDITED }
fun getShippingCostCalculator(delivery: Delivery): (Order) -> Double {
    if (delivery == Delivery.EXPEDITED) {
        return { order -> 6 + 2.1 * order.itemcount }
    }
    return { order -> 1.2 * order.itemcount }
}
val hansu = getShippingCostCalculator(Delivery.EXPEDITED)
hansu(Order(10))

二内联函数

lambda表达式会被正常地编译成匿名类。这表示每调用一次lambda表达式,一个额外的类就会被创建。并且如果lambda捕捉了某个变量,那么每次调用的时候都会创建一个新的对象。这绝对回影响运行的效率,万万不能忍,Kotlin给我们提供了inline修饰符来解决这个问题。这个我也不懂,这里提供一个连接

三 控制逻辑关键字

我们知道在java中控制逻辑的关键字有break,continue,return,并且知道:

  • break在循环中跳出一层循环,在其他都是跳出当前代码块。我喜欢叫它跳1,跳2。
  • continue是结束本次循环,继续下次循环。
  • return始终是结束方法(不管有多少嵌套多少逻辑)我喜欢叫它方法终结者

我们java中经常使用这样的代码:

private int find() {
    for (int b = 0; b < 3; b++) {
        if (b == 1) {
            Log.e("rrrrrr", "里边有:" + 1);
            return b;
        }
    }
    return -1;
}

如果找到某个值,我们直接终止方法,那么在kotlin的lambda表达式中他是否还是方法终结者呢?

fun find(): Int {
    val aaa = arrayListOf(0, 1, 2)
    aaa.forEach {
        Log.e("rrrrrr", "里边有" + it)
        if (it == 1) return it
    }

    return -1
}

发现整个方法终止了,并且返回1,所以return也可以在这里叫方法终结者。

注意:只有在以lambda作为参数的函数是内联函数的时候才能从更外层的函数返回,一个非内联函数可以把传给它的lambda保存在变量中,以便在函数返回以后可以继续使用,这个时候lambda想要去影响函数的返回己经太晚了。

在java中我们使用continue结束本次循环,继续下次循环。那么在Kotlin中continue却不是关键字,这里我们还是用的return去替代continue的功能,代码如下:

fun find() {
    val aaa = arrayListOf(0, 1, 2,3,4,5,6,7,8,9)
    aaa.forEach label@ {
        if (it == 1) return@label
        Log.e("rrrrrrrrr", "rrrrrrr" + it)
    }
    Log.e("rrrrrrrr", "这里return就不是方法终结者了")
}

这里的return就相当于java中的continue的功能。

在java中我们使用break跳出一层循环,那么对用的kotlin代码又怎么写呢?

fun find() {
    val aaa = arrayListOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
    kotlin.run breaking@ {
        aaa.forEach continueing@ {
            if (it == 1) return@breaking
            Log.e("rrrrrrrrr", "rrrrrrr" + it)
        }
    }
    Log.e("rrrrrrrr", "这里return就不是方法终结者了")
}

这里吧循环嵌套外部一层,然后return外部,就是他的break的用法。

看了上边的代码虽然实现了功能,但是感觉代码有点乱,我们这里提供另外一种方式去替代java中的break和continue。

匿名函数替代这种写法:

替代break:

fun find() {
    val aaa = arrayListOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
    fun aa() {
        aaa.forEach {
            if (it == 1) return
            Log.e("rrrrrrr", "eeeeee" + it)
        }
    }
    aa()
    Log.e("rrrrrrrr", "这里return就不是方法终结者了")
}

代替continue:

fun find() {
    val aaa = arrayListOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
    aaa.forEach (fun (x){ if (x==1)return })
}

这里需要说明一下:return从最近的使用fun关键字声明的函数返回。

return总结:

在默认情况下(实现了内联):return从最近的使用fun关键字声明的函数返回,我们可以使用 名字@ 这种方式,让return的位置发生变化,也是java中的break和continue的功能,这里使用匿名方法和@两种方式去实现。

小结:

  • 以另外一个方法做为参数或者返回值的函数叫做高阶函数
  • 函数类型的写法 :(类型)->返回值类型
  • 简单高阶函数的写法
  • 高阶函数在java中实际是利用内部类的方法调用的invike()
  • 方法做为参数可以有默认值
  • 返回类型是函数的写法
  • 内联解决性能调用问题,内联其实就是编译器替换掉代码
  • 控制关键字return的用法,@和匿名函数两种写法